Compare commits

..

10 Commits

Author SHA1 Message Date
48771f54dd f 2023-05-14 18:02:37 +09:00
374196acce init 3 2022-08-22 14:23:34 +09:00
d24d3a63c8 init 2 2022-08-20 10:09:52 +09:00
dd9664d4ee script 2022-08-16 23:18:41 +09:00
SlyCedix
41e3b3fbcd Add archival message 2022-01-09 20:51:08 -05:00
SlyCedix
510ddf4b22 Add remote debugging setup created by DarkMio 2022-01-09 20:43:51 -05:00
Cyn
e0f16244db
Merge pull request #1 from jornedesmedt/main
Added @ns tsconfig.js, used in snippet + example
2022-01-06 15:47:51 -05:00
Jorne De Smedt
f2e297f1e0 Added @ns tsconfig.js, used in snippet + example 2022-01-06 21:14:42 +01:00
Cyn
59e5eef9b1 Update template for new extension link 2022-01-05 19:35:19 -05:00
Cyn
7aa19ac2c6 Ignore NetscriptDefinitions.d.ts in lint 2021-12-29 13:43:11 -05:00
65 changed files with 7808 additions and 337 deletions

View File

@ -14,323 +14,28 @@ module.exports = {
},
},
plugins: ["@typescript-eslint"],
ignorePatterns: ['NetscriptDefinitions.d.ts', '*.js'],
rules: {
"accessor-pairs": [
"@typescript-eslint/array-type": ["error", { "default": "array-simple" }],
"@typescript-eslint/ban-ts-comment": ["off"],
"@typescript-eslint/explicit-member-accessibility": ["off"],
"@typescript-eslint/explicit-module-boundary-types": ["off"],
"@typescript-eslint/no-non-null-assertion": ["off"],
"@typescript-eslint/no-use-before-define": ["off"],
"@typescript-eslint/explicit-function-return-type":["off"],
"@typescript-eslint/no-parameter-properties": ["off"],
"@typescript-eslint/no-unused-vars": [
"error",
{
setWithoutGet: true,
getWithoutSet: false,
},
{ "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }
],
"array-bracket-newline": ["off"],
"array-bracket-spacing": ["off"],
"array-callback-return": ["off"],
"array-element-newline": ["off"],
"arrow-body-style": ["off"],
"arrow-parens": ["off"],
"arrow-spacing": ["off"],
"block-scoped-var": ["off"],
"block-spacing": ["off"],
"brace-style": ["off"],
"callback-return": ["error"],
camelcase: ["off"],
"capitalized-comments": ["off"],
"class-methods-use-this": ["off"],
complexity: ["off"],
"consistent-return": ["off"],
"consistent-this": ["off"],
"constructor-super": ["error"],
curly: ["off"],
"default-case": ["off"],
"dot-notation": ["off"],
"eol-last": ["off"],
eqeqeq: ["off"],
"for-direction": ["error"],
"func-call-spacing": ["off"],
"func-name-matching": ["error"],
"func-names": ["off", "never"],
"func-style": ["off"],
"function-paren-newline": ["off"],
"getter-return": [
"error",
{
allowImplicit: false,
},
],
"global-require": ["off"],
"guard-for-in": ["off"],
"handle-callback-err": ["error"],
"id-blacklist": ["error"],
"id-length": ["off"],
"id-match": ["error"],
indent: ["off"],
"indent-legacy": ["off"],
"init-declarations": ["off"],
"key-spacing": ["off"],
"keyword-spacing": ["off"],
"line-comment-position": ["off"],
"linebreak-style": [
"off", // Line endings automatically converted to LF on git commit so probably shouldn't care about it here
],
"lines-around-comment": ["off"],
"lines-around-directive": ["error"],
"lines-between-class-members": ["error"],
"max-depth": ["off"],
"max-len": ["off"],
"max-lines": ["off"],
"max-nested-callbacks": ["error"],
"max-params": ["off"],
"max-statements": ["off"],
"max-statements-per-line": ["off"],
"multiline-comment-style": ["off", "starred-block"],
"multiline-ternary": ["off", "never"],
"new-cap": ["off"],
"new-parens": ["off"],
"newline-after-var": ["off"],
"newline-before-return": ["off"],
"newline-per-chained-call": ["off"],
"no-alert": ["error"],
"no-array-constructor": ["error"],
"no-await-in-loop": ["off"],
"no-bitwise": ["off"],
"no-buffer-constructor": ["error"],
"no-caller": ["error"],
"no-case-declarations": ["error"],
"no-catch-shadow": ["error"],
"no-class-assign": ["error"],
"no-compare-neg-zero": ["error"],
"no-confusing-arrow": ["error"],
"no-console": ["off"],
"no-const-assign": ["error"],
"no-constant-condition": [
"error",
{
checkLoops: false,
},
],
"no-continue": ["off"],
"no-control-regex": ["error"],
"no-debugger": ["error"],
"no-delete-var": ["error"],
"no-div-regex": ["error"],
"no-dupe-args": ["error"],
"no-dupe-class-members": ["error"],
"no-dupe-keys": ["error"],
"no-duplicate-case": ["error"],
"no-duplicate-imports": [
"error",
{
includeExports: true,
},
],
"no-else-return": ["off"],
"no-empty": [
"off",
{
allowEmptyCatch: false,
},
],
"no-empty-character-class": ["error"],
"no-empty-function": ["off"],
"no-empty-pattern": ["error"],
"no-eq-null": ["off"],
"no-ex-assign": ["off"],
"no-extra-boolean-cast": ["error"],
"no-extra-parens": ["off"],
"no-extra-semi": ["off"],
"no-eval": ["off"],
"no-extend-native": ["off"],
"no-extra-bind": ["error"],
"no-extra-label": ["error"],
"no-fallthrough": ["off"],
"no-floating-decimal": ["off"],
"no-func-assign": ["error"],
"no-global-assign": ["error"],
"no-implicit-coercion": ["off"],
"no-implicit-globals": ["error"],
"no-implied-eval": ["error"],
"no-inline-comments": ["off"],
"no-inner-declarations": ["off", "both"],
"no-invalid-regexp": ["error"],
"no-invalid-this": ["off"],
"no-irregular-whitespace": [
"error",
{
skipStrings: false,
skipComments: false,
skipRegExps: false,
skipTemplates: false,
},
],
"no-iterator": ["error"],
"no-label-var": ["error"],
"no-labels": ["off"],
"no-lone-blocks": ["error"],
"no-lonely-if": ["off"],
"no-loop-func": ["off"],
"no-magic-numbers": ["off"],
"no-mixed-operators": ["off"],
"no-mixed-requires": ["error"],
"no-mixed-spaces-and-tabs": ["off"],
"no-multi-assign": ["off"],
"no-multi-spaces": ["off"],
"no-multi-str": ["error"],
"no-multiple-empty-lines": [
"off",
{
max: 1,
},
],
"no-native-reassign": ["error"],
"no-negated-condition": ["off"],
"no-negated-in-lhs": ["error"],
"no-nested-ternary": ["off"],
"no-new": ["error"],
"no-new-func": ["error"],
"no-new-object": ["error"],
"no-new-require": ["error"],
"no-new-symbol": ["error"],
"no-new-wrappers": ["error"],
"no-octal": ["error"],
"no-octal-escape": ["error"],
"no-obj-calls": ["error"],
"no-param-reassign": ["off"],
"no-path-concat": ["error"],
"no-plusplus": ["off"],
"no-process-env": ["off"],
"no-process-exit": ["error"],
"no-proto": ["error"],
"no-prototype-builtins": ["off"],
"no-redeclare": ["off"],
"no-regex-spaces": ["error"],
"no-restricted-globals": ["error"],
"no-restricted-imports": ["error"],
"no-restricted-modules": ["error"],
"no-restricted-properties": [
"off",
{
object: "console",
property: "log",
message: "'log' is too general, use an appropriate level when logging.",
},
],
"no-restricted-syntax": ["error"],
"no-return-assign": ["off"],
"no-return-await": ["error"],
"no-script-url": ["error"],
"no-self-assign": [
"error",
{
props: false,
},
],
"no-self-compare": ["error"],
"no-sequences": ["error"],
"no-shadow": ["off"],
"no-shadow-restricted-names": ["error"],
"no-spaced-func": ["off"],
"no-sparse-arrays": ["error"],
"no-sync": ["error"],
"no-tabs": ["error"],
"no-template-curly-in-string": ["error"],
"no-ternary": ["off"],
"no-this-before-super": ["off"],
"no-throw-literal": ["error"],
"no-trailing-spaces": ["off"],
"no-undef": ["off"],
"no-undef-init": ["error"],
"no-undefined": ["off"],
"no-underscore-dangle": ["off"],
"no-unexpected-multiline": ["error"],
"no-unmodified-loop-condition": ["error"],
"no-unneeded-ternary": ["off"],
"no-unreachable": ["off"],
"no-unsafe-finally": ["error"],
"no-unsafe-negation": ["error"],
"no-unused-expressions": ["off"],
"no-unused-labels": ["error"],
"no-unused-vars": ["off"],
"no-use-before-define": ["off"],
"no-useless-call": ["off"],
"no-useless-computed-key": ["error"],
"no-useless-concat": ["off"],
"no-useless-constructor": ["error"],
"no-useless-escape": ["off"],
"no-useless-rename": [
"error",
{
ignoreDestructuring: false,
ignoreExport: false,
ignoreImport: false,
},
],
"no-useless-return": ["off"],
"no-var": ["off"],
"no-void": ["off"],
"no-warning-comments": ["off"],
"no-whitespace-before-property": ["error"],
"no-with": ["error"],
"nonblock-statement-body-position": ["off", "below"],
"object-curly-newline": ["off"],
"object-curly-spacing": ["off"],
"object-property-newline": ["off"],
"object-shorthand": ["off"],
"one-var": ["off"],
"one-var-declaration-per-line": ["off"],
"operator-assignment": ["off"],
"operator-linebreak": ["off", "none"],
"padded-blocks": ["off"],
"padding-line-between-statements": ["error"],
"prefer-arrow-callback": ["off"],
"prefer-const": ["off"],
"prefer-destructuring": ["off"],
"prefer-numeric-literals": ["error"],
"prefer-promise-reject-errors": ["off"],
"prefer-reflect": ["off"],
"prefer-rest-params": ["off"],
"prefer-spread": ["off"],
"prefer-template": ["off"],
"quote-props": ["off"],
quotes: ["off"],
radix: ["off", "as-needed"],
"require-await": ["off"],
"require-jsdoc": ["off"],
"require-yield": ["error"],
"rest-spread-spacing": ["error", "never"],
semi: ["warn", "never"],
"semi-spacing": ["off"],
"semi-style": ["error", "last"],
"sort-imports": ["off"],
"sort-keys": ["off"],
"sort-vars": ["off"],
"space-before-blocks": ["off"],
"space-before-function-paren": ["off"],
"space-in-parens": ["off"],
"space-infix-ops": ["off"],
"space-unary-ops": ["off"],
"spaced-comment": ["off"],
strict: ["off"],
"switch-colon-spacing": [
"error",
{
after: true,
before: false,
},
],
"symbol-description": ["error"],
"template-curly-spacing": ["error"],
"template-tag-spacing": ["error"],
"unicode-bom": ["error", "never"],
"use-isnan": ["error"],
"valid-jsdoc": ["off"],
"valid-typeof": ["error"],
"vars-on-top": ["off"],
"wrap-iife": ["error", "any"],
"wrap-regex": ["off"],
"yield-star-spacing": ["error", "before"],
yoda: ["error", "never"],
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/ban-ts-ignore": ["off"],
"@typescript-eslint/no-empty-function": ["off"],
"no-return-await": "error",
"require-await": "error",
"no-async-promise-executor": "error",
"no-constant-condition": ["off"]
},
overrides: [
{

1
.gitignore vendored
View File

@ -1,5 +1,4 @@
node_modules/
dist/
package-lock.json
OAuth.js
NetscriptDefinitions.d.ts

View File

@ -2,6 +2,6 @@
"recommendations": [
"dbaeumer.vscode-eslint",
"Gruntfuggly.auto-snippet",
"hexnaught.vscode-bitburner-connector"
"bitburner.bitburner-vscode-integration"
]
}

20
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,20 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Attach to Bitburner Render Process",
"address": "127.0.0.1",
"port": 9222,
"request": "attach",
"type": "pwa-chrome",
"timeout": 15000,
"webRoot": "${workspaceRoot}/src",
"sourceMapPathOverrides": {
"http://localhost:8000/sources/*": "${workspaceRoot}/src/*"
}
},
]
}

View File

@ -6,9 +6,12 @@
"eslint.workingDirectories": ["./src"],
"editor.codeActionsOnSave": {"source.fixAll.eslint": true},
"bitburner.authToken": "MOG9GEp4MGcw4aupPiqC6C9kd8jtFR2JgWcuS9JMyJjYfRCv0sqWdoB1H0G7HMQY",
"bitburner.fileWatcher.enable": true,
// Bitburner Extension Settings
"bitburner.scriptRoot": "./dist/",
// Autosave Settings
"files.autoSave": "off",

View File

@ -3,7 +3,7 @@
"scope": "typescript",
"prefix": "template",
"body": [
"import { NS } from '../NetscriptDefinitions'",
"import { NS } from '@ns'",
"",
"export async function main(ns : NS) : Promise<void> {",
"\t//",
@ -15,10 +15,29 @@
"prefix": "autocomplete",
"body": [
"// eslint-disable-next-line @typescript-eslint/no-unused-vars",
"export function autocomplete(data : ServerData, args : string[]) : string[] {",
"export function autocomplete(data : AutocompleteData, args : string[]) : string[] {",
"\treturn [...data.servers]",
"}"
],
"description": "autocomplete"
},
"template-advanced":{
"scope": "typescript",
"prefix": "template-advanced",
"body": [
"import { NS } from '@ns'",
"import { parse } from './lib/flag';",
"import { sprintf } from './lib/printf';",
"",
"export async function main(ns: NS) : Promise<void> {",
" const flag = parse(ns.args.map(x=>x.toString()));",
" if(flag.h || flag.help){",
" const msg = ['run cmd']",
" msg.forEach(x=>ns.tprint(x));",
" return;",
" }",
"}"
],
"description": "autocomplete"
}
}

View File

@ -1,5 +1,8 @@
# This repository has been archived
**For the most up to date version of this template, visit the official repository at https://github.com/bitburner-official/vscode-template**
## Extension Recommendations
[vscode-bitburner-connector](https://github.com/hexnaught/vscode-bitburner-connector) ([vscode extension marketplace](https://marketplace.visualstudio.com/items?itemName=hexnaught.vscode-bitburner-connector)) to upload your files into the game
[vscode-bitburner-connector](https://github.com/bitburner-official/bitburner-vscode) ([vscode extension marketplace](https://marketplace.visualstudio.com/items?itemName=bitburner.bitburner-vscode-integration)) to upload your files into the game
[vscode-eslint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) to use live linting in editor
@ -30,3 +33,7 @@ To update your Netscript Definitions, run `npm run defs` in a terminal
Press F1 and Select `Bitburner: Enable File Watcher` to enable auto uploading to the game
If you run `watcher.js` in game, the game will automatically detect file changes and restart the associated scripts
## Deugging
For debugging bitburner on Steam you will need to enable a remote debugging port. This can be done by rightclicking bitburner in your Steam library and selecting properties. There you need to add `--remote-debugging-port=9222` [Thanks @DarkMio]

37
build.js Normal file
View File

@ -0,0 +1,37 @@
const fs = require("fs");
const path = require("path");
const esbuild = require("esbuild");
const yargs = require("yargs");
yargs.scriptName("builder")
.usage("$0 <cmd> [args]")
.command("watch","watch and build",(yargs)=>{
},(argv)=>{
build({
watch:true,
});
})
.command("build","build",(yargs)=>{},
(argv)=>{
build();
})
.help()
.argv;
async function build(options){
options = options || {};
options.watch = options.watch || false;
let dir = fs.readdirSync("src");
dir = dir.filter(x=>x.endsWith(".ts"));
await esbuild.build({
entryPoints:dir,
bundle:true,
write: true,
treeShaking: true,
watch: options.watch,
platform: "neutral",
target:"es2020",
outdir:"./dist",
});
}

View File

@ -4,8 +4,8 @@
"description": "",
"main": "index.js",
"scripts": {
"watch": "npx tsc -w",
"lint": "eslint . --ext .ts",
"build": "node build.js",
"check": "tsc --noEmit",
"defs": "node ./updateDefs.js"
},
"repository": {
@ -19,10 +19,12 @@
"homepage": "https://github.com/SlyCedix/bitburner-typescript-template#readme",
"devDependencies": {
"@types/node": "^16.4.3",
"@types/yargs": "^17.0.11",
"@typescript-eslint/eslint-plugin": "^4.28.4",
"@typescript-eslint/parser": "^4.28.4",
"eslint": "^7.31.0",
"ts-node": "^9.1.1",
"typescript": "^4.3.5"
"esbuild": "^0.15.3"
},
"dependencies": {
"yargs": "^17.5.1"
}
}
}

46
src/auto-backdoor.ts Normal file
View File

@ -0,0 +1,46 @@
import { NS } from '@ns'
import { parse } from './lib/flag';
import { sprintf } from './lib/printf';
import { selectRootedServerList } from "./lib/servers"
async function propagateBackdoor(ns:NS, hostname: string): Promise<void>{
ns.singularity.connect(hostname);
const list = ns.scan(hostname);
for (const relname of list) {
const info = ns.getServer(relname);
if(info.backdoorInstalled){
continue;
}
if(info.requiredHackingSkill > ns.getHackingLevel()){
continue;
}
ns.tprint(`install backdoor ${relname}`);
ns.singularity.connect(relname);
await ns.singularity.installBackdoor();
ns.singularity.connect(hostname);
}
}
export async function main(ns: NS) : Promise<void> {
const flag = parse(ns.args.map(x=>x.toString()));
if(flag.h || flag.help){
const msg = ['run cmd']
msg.forEach(x=>ns.tprint(x));
return;
}
if(flag._.length === 0){
const servers = selectRootedServerList(ns);
for(const server of servers){
ns.tprint("prop backdoor to "+server.hostname);
await propagateBackdoor(ns,server.hostname);
}
}
else{
// const hostname = flag._[0];
// ns.tprint(sprintf("install backdoor %s",hostname));
// await ns.singularity.installBackdoor(hostname);
// ns.tprint("done");
}
}

19
src/auto-contract.ts Normal file
View File

@ -0,0 +1,19 @@
import { NS } from '@ns'
import { selectAllContract, isSolvable, solveContract } from './lib/contract'
export async function main(ns : NS) : Promise<void> {
const remoteList = selectAllContract(ns);
for (const contract of remoteList) {
if(isSolvable(ns,contract.filename,contract.hostname)){
const [success, reward] = await solveContract(ns,contract.filename,contract.hostname);
if(success){
ns.tprint(`solve ${contract.filename}`);
ns.tprint(reward);
}
else{
ns.tprint("stop")
break;
}
}
}
}

15
src/auto-hacking.ts Normal file
View File

@ -0,0 +1,15 @@
import { selectHackableServerList, hackServer } from "./lib/hack"
import {isRootedServer} from "./lib/servers"
import {NS} from "@ns"
/** @param {NS} ns */
// eslint-disable-next-line require-await
export async function main(ns: NS): Promise<void> {
//ns.disableLog("ALL")
const servers = selectHackableServerList(ns).filter(x => !isRootedServer(x))
servers.forEach(x => {
ns.tprint(`hack ${x.hostname}`)
hackServer(ns, x)
})
}

54
src/batch-cal.ts Normal file
View File

@ -0,0 +1,54 @@
import { NS, AutocompleteData } from '@ns'
import { parse } from './lib/flag';
import { sprintf } from './lib/printf';
import {calculateBatchResource} from './lib/batchbase';
// eslint-disable-next-line require-await
export async function main(ns: NS) : Promise<void> {
const flag = parse(ns.args.map(x=>x.toString()));
if(flag.h || flag.help){
const msg = ['run cmd [startThread] [--hostname || -h] [--graph]']
msg.forEach(x=>ns.tprint(x));
return;
}
if(flag._.length === 0){
ns.tprint("Error. Require Argument");
return;
}
if(!(flag.host)){
ns.tprint("host required");
return;
}
const v = parseInt(flag._[0].toString());
if(isNaN(v)){
ns.tprint("Error. Require Integer. v = " + flag._[0]);
return;
}
const hostname = flag.host ?? flag.t;
const server = ns.getServer(hostname)
if(flag.graph){
const col = [];
for (let i = 1; i < v; i++) {
const info = calculateBatchResource(ns,hostname,i,server.moneyMax,1);
if(info === null) break;
col.push({
costEffect :(info.earnMoneyPerCycle / info.totalThreadCount),
index: i,
total: info.totalThreadCount
})
}
col.forEach(({costEffect,index,total})=>{
ns.tprint(sprintf("%3d %10.2f %4d",index,costEffect,total));
})
ns.tprint(col.map(x=>x.costEffect))
return;
}
const info = calculateBatchResource(ns,hostname,v,server.moneyMax,1);
ns.tprint(JSON.stringify(info,undefined,2));
return;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function autocomplete(data : AutocompleteData, args : string[]) : string[] {
return [...data.servers]
}

46
src/batching.ts Normal file
View File

@ -0,0 +1,46 @@
import { NS, AutocompleteData } from '@ns'
import { installBatchFilePack, isBatchFilePackInstalled, scpBatchFilePack, batchDaemon, requestHWGW } from './lib/batchbase';
import { parse } from './lib/flag';
function help(ns:NS):void{
ns.tprint("run cmd [target(hostname)] --hostname hostname")
}
// eslint-disable-next-line require-await
export async function main(ns : NS) : Promise<void> {
const flag = parse(ns.args.map(String))
const hostname: string|undefined = flag.hostname ?? flag.n;
if(!hostname){
ns.tprint("hostname is not set");
help(ns);
return;
}
if(flag._.length == 0){
ns.tprint("target is not set");
help(ns);
return;
}
const target = flag._[0].toString();
ns.print("check batch file pack ...")
if(!isBatchFilePackInstalled(ns)){
await installBatchFilePack(ns);
}
const server = ns.getServer(hostname)
await scpBatchFilePack(ns,hostname);
ns.print("request hwgw ...")
requestHWGW({
hostname,
usableRam: server.maxRam - server.ramUsed,
},target);
ns.print("request hwgw done")
ns.print("start batch daemon ...");
await batchDaemon(ns, target);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function autocomplete(data : AutocompleteData, args : string[]) : string[] {
return [...data.servers]
}

45
src/deploy-miner.ts Normal file
View File

@ -0,0 +1,45 @@
import { AutocompleteData, NS } from "@ns";
import {parse} from "./lib/flag";
/** @param {NS} ns */
export async function main(ns: NS): Promise<void> {
const flag = parse(ns.args.map(String));
if(flag._.length < 2){
ns.tprint("argument required");
ns.tprint("run cmd [src(hostname)] [dest(hostname)]");
ns.exit();
}
const src = String(flag._[0]);
const dest = String(flag._[1]);
const th = flag.thread || false;
ns.tprint(`scp /script/mining.js ${src}`);
await ns.scp("/script/mining.js",src);
let nThread;
if(th){
nThread = th;
}
else {
nThread = calculateMaximumThread(ns, src, ns.getScriptRam("/script/mining.js", src));
}
const pid = ns.exec("/script/mining.js",src,nThread,nThread,dest);
ns.tprint(pid," Process started.");
}
/**
* @param {NS} ns
* @param {string} dst
* @param {number} amount the program ram
*/
export function calculateMaximumThread(ns:NS,host:string,amount:number):number{
const maxRam = ns.getServerMaxRam(host);
const usedRam = ns.getServerUsedRam(host);
const restRam = maxRam - usedRam;
return Math.floor(restRam / amount);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function autocomplete(data: AutocompleteData,args: string[]): string[]{
return [...data.servers];
}

45
src/deploy-share.ts Normal file
View File

@ -0,0 +1,45 @@
import { AutocompleteData, NS } from "@ns";
import {parse} from "./lib/flag";
import { installBatchFilePack, isBatchFilePackInstalled, scpBatchFilePack, execWeakenLoop, getRamOfScript } from './lib/batchbase';
/** @param {NS} ns */
export async function main(ns: NS): Promise<void> {
const flag = parse(ns.args.map(String));
if(flag._.length < 1){
ns.tprint("argument required");
ns.tprint("run cmd [src(hostname)]");
ns.exit();
}
const hostname = String(flag._[0]);
const th = flag.thread || false;
ns.scp("share-server.js",hostname);
let nThread;
if(th){
nThread = th;
}
else {
nThread = calculateMaximumThread(ns, hostname, ns.getScriptRam("share-server.js",hostname));
}
const pid = ns.exec("share-server.js",hostname, nThread);
ns.tprint(pid," Process started.");
}
/**
* @param {NS} ns
* @param {string} dst
* @param {number} amount the program ram
*/
export function calculateMaximumThread(ns:NS,host:string,amount:number):number{
const maxRam = ns.getServerMaxRam(host);
const usedRam = ns.getServerUsedRam(host);
const restRam = maxRam - usedRam;
return Math.floor(restRam / amount);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function autocomplete(data: AutocompleteData,args: string[]): string[]{
return [...data.servers];
}

57
src/deploy-weaken.ts Normal file
View File

@ -0,0 +1,57 @@
import { AutocompleteData, NS } from "@ns";
import {parse} from "./lib/flag";
import { installBatchFilePack, isBatchFilePackInstalled, scpBatchFilePack, execWeakenLoop, getRamOfScript } from './lib/batchbase';
/** @param {NS} ns */
export async function main(ns: NS): Promise<void> {
const flag = parse(ns.args.map(String));
if(flag._.length < 2){
ns.tprint("argument required");
ns.tprint("run cmd [src(hostname)] [dest(hostname)]");
ns.exit();
}
const src = String(flag._[0]);
const dest = String(flag._[1]);
const th = flag.thread || false;
if(!isBatchFilePackInstalled(ns)){
await installBatchFilePack(ns);
}
await scpBatchFilePack(ns,src);
const stock = flag.stock ?? false;
let nThread;
if(th){
nThread = th;
}
else {
nThread = calculateMaximumThread(ns, src, getRamOfScript(ns,"weakenLoop"));
}
const pid = execWeakenLoop(ns,{
hostname: src,
target: dest,
numThread: nThread,
stock: stock,
});
ns.tprint(pid," Process started.");
}
/**
* @param {NS} ns
* @param {string} dst
* @param {number} amount the program ram
*/
export function calculateMaximumThread(ns:NS,host:string,amount:number):number{
const maxRam = ns.getServerMaxRam(host);
const usedRam = ns.getServerUsedRam(host);
const restRam = maxRam - usedRam;
return Math.floor(restRam / amount);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function autocomplete(data: AutocompleteData,args: string[]): string[]{
return [...data.servers];
}

18
src/get-cylce-size.ts Normal file
View File

@ -0,0 +1,18 @@
import { NS } from '@ns'
export async function main(ns : NS) : Promise<void> {
const stat: number[] = [];
const M = 10;
for (let i = 0; i < 100; i++) {
const start = performance.now();
await ns.sleep(M);
const end = performance.now();
stat.push(end- start);
}
const avg = stat.reduce((x,y)=>x+y)/stat.length;
const max = Math.max(...stat);
const min = Math.min(...stat);
ns.tprint(`avg : ${avg - M}`);
ns.tprint(`max : ${max - M}`);
ns.tprint(`min : ${min - M}`);
}

16
src/get-my-karma.ts Normal file
View File

@ -0,0 +1,16 @@
import { NS } from '@ns'
import { parse } from './lib/flag';
import { sprintf } from './lib/printf';
// eslint-disable-next-line require-await
export async function main(ns: NS) : Promise<void> {
const flag = parse(ns.args.map(x=>x.toString()));
if(flag.h || flag.help){
const msg = ['run cmd']
msg.forEach(x=>ns.tprint(x));
return;
}
// typescript error ignore
// @ts-ignore TS2339
ns.tprint(sprintf("karma: %.3f",ns.heart.break()));
}

6
src/get-share-power.ts Normal file
View File

@ -0,0 +1,6 @@
import {NS} from "@ns"
// eslint-disable-next-line require-await
export async function main(ns:NS):Promise<void>{
ns.tprint(ns.getSharePower());
}

46
src/growing.ts Normal file
View File

@ -0,0 +1,46 @@
import { NS, AutocompleteData } from '@ns'
import { installBatchFilePack, isBatchFilePackInstalled, scpBatchFilePack, growDaemon, requestGrow } from './lib/batchbase';
import { parse } from './lib/flag';
function help(ns:NS):void{
ns.tprint("run cmd [target(hostname)] --hostname hostname")
}
// eslint-disable-next-line require-await
export async function main(ns : NS) : Promise<void> {
const flag = parse(ns.args.map(String))
const hostname: string|undefined = flag.hostname ?? flag.n;
if(!hostname){
ns.tprint("hostname is not set");
help(ns);
return;
}
if(flag._.length == 0){
ns.tprint("target is not set");
help(ns);
return;
}
const target = flag._[0].toString();
ns.print("check batch file pack ...")
if(!isBatchFilePackInstalled(ns)){
await installBatchFilePack(ns);
}
const server = ns.getServer(hostname)
await scpBatchFilePack(ns,hostname);
ns.print("request hwgw ...")
requestGrow({
hostname,
usableRam: server.maxRam - server.ramUsed,
},target);
ns.print("request hwgw done")
ns.print("start batch daemon ...");
await growDaemon(ns, target);
ns.print("end grow daemon");
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function autocomplete(data : AutocompleteData, args : string[]) : string[] {
return [...data.servers]
}

235
src/hacknet-daemon.ts Normal file
View File

@ -0,0 +1,235 @@
import { NS } from "@ns"
import { parse } from "./lib/flag";
import { range } from "./util/range";
class Account {
value: number;
constructor(value: number) {
this.value = value;
}
/**
*
* @param ns NS
* @param rate reinvestment rate
* @param period the time since last update. it's millisecond unit.
*/
upgradeIncrement(ns: NS, rate: number, period: number): void {
const production = [...getIndexOfHackNode(ns)]
.map(x => ns.hacknet.getNodeStats(x).production)
.reduce((x, y) => x + y);
this.value += production * rate * (period / 1000);
}
formatedValue(ns: NS): string{
return ns.nFormat(this.value,"$0.0000a");
}
}
export function getNodeProduction(level: number, ram: number, cores: number, mult: number, bitMult: number): number {
const moneyGainPerLevel = 1.5;
const levelMult = level * moneyGainPerLevel;
const ramMult = Math.pow(1.035, ram - 1);
const coreMult = ((cores + 5) / 6);
return levelMult * ramMult * coreMult * mult * bitMult;
}
interface HacknetNodeComponent<T> {
level: T;
core: T;
ram: T;
}
type ComponentKind = "level" | "core" | "ram";
export interface CEInfo {
upgradeCost: HacknetNodeComponent<number>;
production: HacknetNodeComponent<number>;
costEffective: HacknetNodeComponent<number>;
}
interface Factor<T> {
cost: T;
production: T;
costEffective: T;
}
function CEInfoToFactor(ceinfo: CEInfo):HacknetNodeComponent<Factor<number>>{
return {
level:{
cost:ceinfo.upgradeCost.level,
production:ceinfo.production.level,
costEffective:ceinfo.costEffective.level,
},
core:{
cost:ceinfo.upgradeCost.core,
production:ceinfo.production.core,
costEffective:ceinfo.costEffective.core,
},
ram:{
cost:ceinfo.upgradeCost.ram,
production:ceinfo.production.ram,
costEffective:ceinfo.costEffective.ram,
}
}
}
function getCostEffective(ns: NS, nodeIndex: number, n: number): CEInfo {
const coreUpgradeCost = ns.hacknet.getCoreUpgradeCost(nodeIndex, n);
const ramUpgradeCost = ns.hacknet.getRamUpgradeCost(nodeIndex, n);
const levelUpgradeCost = ns.hacknet.getLevelUpgradeCost(nodeIndex, n);
const mult = ns.getHacknetMultipliers().production;
const bitMult = 1//ns.getBitNodeMultipliers().HacknetNodeMoney;
const stat = ns.hacknet.getNodeStats(nodeIndex);
const coreProduction = getNodeProduction(stat.level, stat.ram, stat.cores + n, mult, bitMult) - stat.production;
const levelProduction = getNodeProduction(stat.level + n, stat.ram, stat.cores, mult, bitMult) - stat.production;
const ramProduction = getNodeProduction(stat.level, stat.ram * Math.pow(2, n), stat.cores, mult, bitMult) - stat.production;
return {
upgradeCost: {
core: coreUpgradeCost,
ram: ramUpgradeCost,
level: levelUpgradeCost,
},
production: {
core: coreProduction,
ram: ramProduction,
level: levelProduction,
},
costEffective: {
core: coreProduction / coreUpgradeCost,
ram: ramProduction / ramUpgradeCost,
level: levelProduction / levelUpgradeCost
}
}
}
function createUpgradeAction(ns: NS, index: number, n: number, kind: ComponentKind): (account: Account) => void {
return (account: Account) => {
switch (kind) {
case "core":
account.value -= ns.hacknet.getCoreUpgradeCost(index, n);
ns.hacknet.upgradeCore(index, n)
break;
case "level":
account.value -= ns.hacknet.getLevelUpgradeCost(index, n);
ns.hacknet.upgradeLevel(index, n)
break;
case "ram":
account.value -= ns.hacknet.getRamUpgradeCost(index, n);
ns.hacknet.upgradeRam(index, n)
break;
}
}
}
function getMaxCENode(ns: NS, index: number, n: number): [Factor<number>, ComponentKind] {
const a = getCostEffective(ns, index, n);
const b = CEInfoToFactor(a);
const ce = a.costEffective;
if (ce.core > ce.level) {
if (ce.core > ce.ram) {
return [b.core, "core"];
}
else {
return [b.ram, "ram"];
}
}
else {
if (ce.level > ce.ram) {
return [b.level, "level"];
}
else {
return [b.ram, "ram"];
}
}
}
function* getIndexOfHackNode(ns: NS): Generator<number> {
const numNodes = ns.hacknet.numNodes()
for (const it of range(numNodes)) {
yield it;
}
}
function getMaxCEIndex(ns: NS, n: number): [number, Factor<number>, ComponentKind] {
let optimalC: Factor<number> = {
cost:0,
production:0,
costEffective:0,
};
let kind: ComponentKind = "core";
let maxIndex = 0;
for (const it of getIndexOfHackNode(ns)) {
const [c, k] = getMaxCENode(ns, it, n);
if (c.costEffective > optimalC.costEffective) {
optimalC = c;
kind = k;
maxIndex = it;
}
}
return [maxIndex, optimalC, kind];
}
function getBuyNodeCE(ns: NS): Factor<number> {
const mult = ns.getHacknetMultipliers().production;
const bitMult = 1//ns.getBitNodeMultipliers().HacknetNodeMoney;
const cost = ns.hacknet.maxNumNodes() > ns.hacknet.numNodes() ? ns.hacknet.getPurchaseNodeCost() : Infinity;
const production = getNodeProduction(1, 1, 1, mult, bitMult);
return {
cost,
production,
costEffective: production/cost,
};
}
function cycle(ns: NS, account: Account): void {
const chunkN = 1;
const [upgradeNodeIndex, upgradeNodeCE, kind] = getMaxCEIndex(ns, chunkN);
const { costEffective: buyNewCE, cost: buyNewCost } = getBuyNodeCE(ns);
if (upgradeNodeCE.costEffective > buyNewCE) {
if(account.value > upgradeNodeCE.cost){
ns.print(`upgrade ${kind} index ${upgradeNodeIndex} : CE ${upgradeNodeCE.costEffective}`);
const action = createUpgradeAction(ns, upgradeNodeIndex, chunkN, kind);
action(account);
ns.print(`budget ${account.formatedValue(ns)}`);
}
}
else {
if (account.value > buyNewCost) {
ns.print(`purchase node : CE ${buyNewCE}`);
account.value -= buyNewCost;
ns.hacknet.purchaseNode()
ns.print(`budget ${account.formatedValue(ns)}`);
}
}
}
// eslint-disable-next-line require-await
export async function main(ns: NS): Promise<void> {
ns.disableLog("ALL")
const flag = parse(ns.args.map(String));
if(flag.h || flag.help){
ns.tprint(`run cmd [-i or --iter] [-r or --rate] [-b --budget]`);
ns.tprint(`-i : cycle iterating count`);
ns.tprint(`-r : reinvestment rate`);
ns.tprint(`-b : initial budget`);
return;
}
const iter = parseInt(flag.i ?? flag.iter ?? "10");
const rate = parseFloat(flag.r ?? flag.rate ?? "0.5");
const budget = parseInt(flag.b ?? flag.budget ?? "0");
const account = new Account(budget);
ns.tail();
const period = 100;
for (let i = 0; i < iter; i++) {
//ns.clearLog()
cycle(ns, account);
await ns.sleep(period);
account.upgradeIncrement(ns,rate*0.1,period);
}
}

531
src/lib/batchbase.ts Normal file
View File

@ -0,0 +1,531 @@
import { NS, Server } from "@ns"
import { info } from "console";
import { calculateServerGrowth, FormulaHelper } from "./formula";
import { Scheduler } from "./scheduler";
export type BatchType = "hack" | "grow" | "weaken" | "weakenLoop";
type BatchFilePackEntry = {
type: BatchType;
path: string;
content: string;
ram: number;
};
type FilePack = BatchFilePackEntry[];
const createScriptContent: (f: string) => string = (funcName: string) => `export async function main(ns) {
if(ns.args.length < 3){
ns.print("[ERROR] args required");
ns.print("run cmd [sleepDuration] [hostname] [effectStock]")
ns.exit();
return;
}
const sleepDuration = parseInt(ns.args[0]);
const hostname = ns.args[1].toString();
const stock = ns.args[2] == "true";
if(sleepDuration > 0){
await ns.sleep(sleepDuration);
}
await ns.${funcName}(hostname,{
stock,
});
}`
async function unpackFileEntry(ns: NS, filePack: BatchFilePackEntry): Promise<void> {
await ns.write(filePack.path, filePack.content, "w");
}
export async function unpackFilePack(ns: NS, pack: FilePack): Promise<void> {
for (const entry of pack) {
await unpackFileEntry(ns, entry);
}
}
export const batchFilePack: FilePack = [
{
type: "hack",
path: "/tmp/hack.js",
content: createScriptContent("hack"),
ram:1.7,
},
{
type: "weaken",
path: "/tmp/weaken.js",
content: createScriptContent("weaken"),
ram: 1.75,
},
{
type: "grow",
path: "/tmp/grow.js",
content: createScriptContent("grow"),
ram: 1.75,
},
{
type:"weakenLoop",
path: "/tmp/weakenLoop.js",
ram: 1.75,
content: `export async function main(ns) {
if(ns.args.length < 3){
ns.print("[ERROR] args required");
ns.print("run cmd [sleepDuration] [hostname] [effectStock]")
ns.exit();
return;
}
const sleepDuration = parseInt(ns.args[0]);
const hostname = ns.args[1].toString();
const stock = ns.args[2] == "true";
for(;;){
await ns.weaken(hostname,{
stock,
});
}
}`,
}
]
export async function scpBatchFilePack(ns: NS, hostname: string): Promise<void> {
const list = batchFilePack.map(entry => entry.path);
await installBatchFilePack(ns);
await ns.scp(list, hostname);
}
export async function installBatchFilePack(ns: NS): Promise<void> {
await unpackFilePack(ns, batchFilePack);
}
export function isBatchFilePackInstalled(ns: NS, hostname?: string): boolean {
return batchFilePack.every(entry => ns.fileExists(entry.path, hostname))
}
export interface ExecOption {
hostname: string;
/**
* @default 1
*/
numThread?: number;
/**
* milliseconds to sleep before exec
* @default 0
*/
sleepDuration?: number;
/**
* target hostname to operate "hack", "weaken" or "grow"
*/
target: string;
stock?: boolean;
}
function execBatchfile(ns: NS, path: string, option: ExecOption): number {
const uuid = crypto.randomUUID();
return ns.exec(path, option.hostname, option.numThread,
option.sleepDuration ?? 0, option.target, option.stock ?? false, uuid);
}
export function execHack(ns: NS, option: ExecOption): number {
return execBatchfile(ns, "/tmp/hack.js", option);
}
export function execGrow(ns: NS, option: ExecOption): number {
return execBatchfile(ns, "/tmp/grow.js", option);
}
export function execWeaken(ns: NS, option: ExecOption): number {
return execBatchfile(ns, "/tmp/weaken.js", option);
}
export function execWeakenLoop(ns: NS, option: ExecOption): number {
return execBatchfile(ns, "/tmp/weakenLoop.js", option);
}
export function execScriptOf(ns: NS,type: BatchType, option:ExecOption):number {
return execBatchfile(ns, "/tmp/" + type + ".js", option);
}
export function getRamOfScript(ns: NS, type: BatchType): number{
return batchFilePack.filter(x=>x.type == type)[0].ram;
}
type ReserveRequest = {
hostname: string;
usableRam: number;
}
function getKeyOfHWGWBatchManager(hostname: string): string{
return `HWGW-${hostname}`;
}
interface HWGW {
HThreadCount: number;
HWThreadCount: number;
GThreadCount: number;
GWThreadCount: number;
}
function getTotalRamOf(hwgw: HWGW): number{
return hwgw.HThreadCount * 1.7 + hwgw.HWThreadCount * 1.75 + hwgw.GThreadCount * 1.75 + hwgw.GWThreadCount * 1.75;
}
type Resource = {
hostname: string;
usableRam: number;
};
interface HackCalculation extends HWGW {
earnMoneyPerCycle: number;
earnExpPerCycle: number;
/**
* cycle time in milliseconds.
*/
cycleTime: number;
totalThreadCount: number;
weakenTime: number;
growTime: number;
hackTime: number;
}
export function calculateBatchResource(ns: NS, hostname: string, hackThread: number, startMoney?: number, cores = 1): HackCalculation| null {
const helper = new FormulaHelper(ns);
const server = ns.getServer(hostname);
startMoney ??= server.moneyMax;
server.hackDifficulty = server.minDifficulty;
const percentMoneyHacked = helper.calculatePercentMoneyHacked(server);
const hackingChance = helper.calculateHackingChance(server);
const earnMoneyPerCycle = hackThread * percentMoneyHacked * startMoney * hackingChance;
const earnExpPerCycle = helper.calculateHackingExpGain(server) * hackThread * hackingChance;
const cycleTime = 1000 + helper.calculateWeakenTime(server);
const increasedSecurityAfterHack = helper.calculateHackSecurity(hackThread) + server.minDifficulty;
const nWeakenHackThreads = Math.ceil(helper.calculateWeakenThreadCountToTargetSecurity(
server.minDifficulty,
increasedSecurityAfterHack, cores));
if(startMoney <= earnMoneyPerCycle){
return null;
}
const nGrowThreads = Math.ceil(helper.calculateGrowThreadCount(hostname, startMoney - earnMoneyPerCycle, startMoney, cores));
const increasedSecurityAfterGrow = helper.calculuateGrowthSecurity(nGrowThreads, cores) + server.minDifficulty;
const nWeakenGrowThreads = Math.ceil(helper.calculateWeakenThreadCountToTargetSecurity(
server.minDifficulty,
increasedSecurityAfterGrow, cores));
return {
weakenTime: helper.calculateWeakenTime(server),
growTime: helper.calculateGrowTime(server),
hackTime: helper.calculateHackingTime(server),
earnMoneyPerCycle,
earnExpPerCycle,
cycleTime,
totalThreadCount: hackThread + nWeakenHackThreads + nGrowThreads + nWeakenGrowThreads,
HThreadCount: hackThread,
HWThreadCount: nWeakenHackThreads,
GThreadCount: nGrowThreads,
GWThreadCount: nWeakenGrowThreads
}
}
class BatchManager {
server: Server;
ns: NS;
totalRam: number
scheluder: Scheduler;
resources: Resource[] = [];
constructor(ns: NS, hostname: string) {
this.ns = ns;
this.server = ns.getServer(hostname);
this.totalRam = 0;
this.scheluder = new Scheduler(ns, 250);
}
reserve(hostname: string, usableRam: number): void {
this.resources.push({
hostname,
usableRam,
});
this.totalRam += usableRam;
}
getUsableTotalRam(): number {
return this.resources.reduce((acc, cur) => acc + cur.usableRam, 0);
}
execute(type: BatchType, opt: Omit<ExecOption, "hostname">): number[] {
const ret = [];
let numThread = opt.numThread ?? 1;
const ramUnit = getRamOfScript(this.ns, type);
for (let i = 0; i < this.resources.length; i++) {
const resource = this.resources[i]
let threadToRun = Math.floor(resource.usableRam / ramUnit);
if (threadToRun > numThread) {
threadToRun = numThread;
}
if (threadToRun <= 0) continue;
const pid = execScriptOf(this.ns, type, {
hostname: resource.hostname,
numThread: threadToRun,
sleepDuration: opt.sleepDuration,
target: opt.target,
stock: opt.stock,
});
resource.usableRam -= threadToRun * ramUnit;
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
const doWork = ()=>{
resource.usableRam += threadToRun * ramUnit;
const msg = [
`${pid} has finished.`,
`${resource.hostname} recover ${threadToRun * ramUnit} ram`]
this.ns.print(msg.join("\n"));
}
this.scheluder.onPidTerminate(pid, {work() {doWork();}});
numThread -= threadToRun;
ret.push(pid);
}
return ret;
}
}
class HWGWBatchManager extends BatchManager {
constructor(ns: NS, hostname: string) {
super(ns, hostname);
}
calcBatchResource(hackThread: number): HackCalculation| null {
return calculateBatchResource(this.ns, this.server.hostname, hackThread, this.server.moneyMax)
}
/**
* execute batch
* do not await this function.
* @param hwgw HackCalculation of HWGW
* @param stock if true, it influnces stock market.
* @returns set of pids of scripts runned.
*/
async execBatch(hwgw: HackCalculation, stock?: boolean): Promise<number[]> {
stock ??= false;
//this.ns.print(`executing batch of ${hwgw.totalThreadCount} threads.`);
//this.ns.print(`total ram: ${this.totalRam}`);
this.ns.print(`stage 1: executed ${hwgw.HThreadCount} threads.`);
const hpid = this.execute("hack",{
target:this.server.hostname,
numThread:hwgw.HThreadCount,
sleepDuration: hwgw.cycleTime - hwgw.hackTime - 1000,
stock
});
await this.scheluder.sleepInWork(250);
this.ns.print(`stage 2: executed ${hwgw.HWThreadCount} threads.`);
const wpid = this.execute("weaken",{
target:this.server.hostname,
numThread:hwgw.HWThreadCount,
sleepDuration: hwgw.cycleTime - hwgw.weakenTime - 1000,
stock
});
await this.scheluder.sleepInWork(250);
this.ns.print(`stage 3: executed ${hwgw.GThreadCount} threads.`);
const gpid = this.execute("grow",{
target:this.server.hostname,
numThread:hwgw.GThreadCount,
sleepDuration: hwgw.cycleTime - hwgw.growTime - 1000,
stock
});
await this.scheluder.sleepInWork(250);
this.ns.print(`stage 4: executed ${hwgw.GWThreadCount} threads.`);
const gwpid = this.execute("weaken",{
target:this.server.hostname,
numThread:hwgw.GWThreadCount,
sleepDuration: hwgw.cycleTime - hwgw.weakenTime - 1000,
stock
});
await this.scheluder.sleepInWork(250);
return [...hpid, ...wpid, ...gpid, ...gwpid];
}
calcHWGW(): HackCalculation {
const helper = new FormulaHelper(this.ns);
const cycleTime = 1 + helper.calculateWeakenTime(this.server);
const ramPerSec = Math.floor(this.totalRam / (cycleTime / 1000));
const [min,minInfo] = this.getMinThreadCount();
const minRam = getTotalRamOf(minInfo);
if(minRam > ramPerSec){
return minInfo;
}
// linear search
let prev = minInfo;
for (let i = min+1; i < min + 100; i++) {
const info = this.calcBatchResource(i);
if(info === null) break;
if(getTotalRamOf(info) > ramPerSec){
return prev;
}
prev = info;
}
return prev;
}
getMinThreadCount():[number,HackCalculation]{
let minCE = 0;
let minHC = 0;
let minInfo: HackCalculation;
for (let i = 1; i < 100; i++) {
const info = this.calcBatchResource(i);
if(info === null) break;
const ce = info.earnMoneyPerCycle / info.totalThreadCount;
if (ce >= minCE) {
minCE = ce;
minHC = i;
minInfo = info;
}
else break;
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return [minHC,minInfo!];
}
}
export function requestHWGW(req: ReserveRequest, target: string): void {
const key = getKeyOfHWGWBatchManager(target);
const data = localStorage.getItem(key);
let requests: ReserveRequest[];
if (data) {
requests = JSON.parse(data);
}
else {
requests = [];
}
requests.push(req);
localStorage.setItem(key, JSON.stringify(requests));
}
export function popHWGWRequest(hostname: string): ReserveRequest[]{
const key = getKeyOfHWGWBatchManager(hostname);
const text = localStorage.getItem(key)
if(text === null){
return [];
}
localStorage.setItem(key,"[]");
return JSON.parse(text) as ReserveRequest[];
}
export async function batchDaemon(ns: NS, hostname: string): Promise<void> {
ns.disableLog("getBitNodeMultipliers");
ns.disableLog("sleep");
const mgr = new HWGWBatchManager(ns, hostname);
for (;;) {
const requests = popHWGWRequest(hostname);
for (const req of requests) {
mgr.reserve(req.hostname, req.usableRam);
}
const hwgw = mgr.calcHWGW();
if (hwgw !== null && mgr.getUsableTotalRam() >= getTotalRamOf(hwgw)) {
ns.print(`hwgw: total ${hwgw.totalThreadCount} threads.`);
mgr.execBatch(hwgw);
}
for (let i = 0; i < 4; i++) {
await mgr.scheluder.tick();
}
}
}
function getKeyOfGrowBatchManager(hostname: string): string{
return `Grow-${hostname}`;
}
export function popGrowRequest(hostname: string): ReserveRequest[]{
const key = getKeyOfGrowBatchManager(hostname);
const text = localStorage.getItem(key)
if(text === null){
return [];
}
localStorage.setItem(key,"[]");
return JSON.parse(text) as ReserveRequest[];
}
export function requestGrow(req: ReserveRequest, target: string): void {
const key = getKeyOfGrowBatchManager(target);
const data = localStorage.getItem(key);
let requests: ReserveRequest[];
if (data) {
requests = JSON.parse(data);
}
else {
requests = [];
}
requests.push(req);
localStorage.setItem(key, JSON.stringify(requests));
}
class GrowManager extends BatchManager {
constructor(ns: NS, hostname: string) {
super(ns, hostname);
}
async execStraigy(stock: boolean): Promise<void> {
const helper = new FormulaHelper(this.ns);
const difficultyMargin = 1;
if(this.server.hackDifficulty > this.server.minDifficulty + difficultyMargin){
const weakenThread = helper.calculateWeakenThreadCountToTargetSecurity(
this.server.hackDifficulty, this.server.minDifficulty);
const g = this.execute("weaken", {
target: this.server.hostname,
numThread: weakenThread,
sleepDuration: 1,
stock
});
this.ns.print("[PROCESS] weakened");
const work = async (): Promise<void> => {
await this.execStraigy(stock);
}
this.scheluder.onPidTerminate(g[0], {
work
});
}
const moneyFactor = 0.8;
if(this.server.moneyAvailable > this.server.moneyMax * moneyFactor){
return;
}
const totalRam = this.getUsableTotalRam();
const growThread = Math.floor(totalRam / (getRamOfScript(this.ns, "grow") * 3));
const weakenThread = growThread * 2;
const growTime = helper.calculateGrowTime(this.server);
const weakenTime = helper.calculateWeakenTime(this.server);
this.execute("weaken", {
target: this.server.hostname,
numThread: weakenThread,
sleepDuration: 0,
});
await this.scheluder.sleepInWork(250);
const g = this.execute("grow", {
target: this.server.hostname,
numThread: growThread,
sleepDuration: weakenTime - growTime,
stock
});
const work = async (): Promise<void> => {
await this.execStraigy(stock);
}
this.scheluder.onPidTerminate(g[0], {
work
});
}
empty(): boolean {
return this.scheluder.empty();
}
}
export async function growDaemon(ns: NS, hostname: string): Promise<void> {
ns.disableLog("getBitNodeMultipliers");
ns.disableLog("sleep");
const mgr = new GrowManager(ns, hostname);
processRequest();
mgr.execStraigy(false);
while (!mgr.empty()) {
processRequest()
await mgr.scheluder.tick();
}
function processRequest(): void{
const requests = popGrowRequest(hostname);
for (const req of requests) {
mgr.reserve(req.hostname, req.usableRam);
}
}
}

536
src/lib/colors.ts Normal file
View File

@ -0,0 +1,536 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
// A module to print ANSI terminal colors. Inspired by chalk, kleur, and colors
// on npm.
/**
* String formatters and utilities for dealing with ANSI color codes.
*
* This module is browser compatible.
*
* ```ts
* import { bgBlue, red, bold } from "https://deno.land/std@$STD_VERSION/fmt/colors.ts";
* console.log(bgBlue(red(bold("Hello world!"))));
* ```
*
* This module supports `NO_COLOR` environmental variable disabling any coloring
* if `NO_COLOR` is set.
*
* @module
*/
const noColor = false;
interface Code {
open: string;
close: string;
regexp: RegExp;
}
/** RGB 8-bits per channel. Each in range `0->255` or `0x00->0xff` */
interface Rgb {
r: number;
g: number;
b: number;
}
let enabled = !noColor;
/**
* Set changing text color to enabled or disabled
* @param value
*/
export function setColorEnabled(value: boolean) {
if (noColor) {
return;
}
enabled = value;
}
/** Get whether text color change is enabled or disabled. */
export function getColorEnabled(): boolean {
return enabled;
}
/**
* Builds color code
* @param open
* @param close
*/
function code(open: number[], close: number): Code {
return {
open: `\x1b[${open.join(";")}m`,
close: `\x1b[${close}m`,
regexp: new RegExp(`\\x1b\\[${close}m`, "g"),
};
}
/**
* Applies color and background based on color code and its associated text
* @param str text to apply color settings to
* @param code color code to apply
*/
function run(str: string, code: Code): string {
return enabled
? `${code.open}${str.replace(code.regexp, code.open)}${code.close}`
: str;
}
/**
* Reset the text modified
* @param str text to reset
*/
export function reset(str: string): string {
return run(str, code([0], 0));
}
/**
* Make the text bold.
* @param str text to make bold
*/
export function bold(str: string): string {
return run(str, code([1], 22));
}
/**
* The text emits only a small amount of light.
* @param str text to dim
*/
export function dim(str: string): string {
return run(str, code([2], 22));
}
/**
* Make the text italic.
* @param str text to make italic
*/
export function italic(str: string): string {
return run(str, code([3], 23));
}
/**
* Make the text underline.
* @param str text to underline
*/
export function underline(str: string): string {
return run(str, code([4], 24));
}
/**
* Invert background color and text color.
* @param str text to invert its color
*/
export function inverse(str: string): string {
return run(str, code([7], 27));
}
/**
* Make the text hidden.
* @param str text to hide
*/
export function hidden(str: string): string {
return run(str, code([8], 28));
}
/**
* Put horizontal line through the center of the text.
* @param str text to strike through
*/
export function strikethrough(str: string): string {
return run(str, code([9], 29));
}
/**
* Set text color to black.
* @param str text to make black
*/
export function black(str: string): string {
return run(str, code([30], 39));
}
/**
* Set text color to red.
* @param str text to make red
*/
export function red(str: string): string {
return run(str, code([31], 39));
}
/**
* Set text color to green.
* @param str text to make green
*/
export function green(str: string): string {
return run(str, code([32], 39));
}
/**
* Set text color to yellow.
* @param str text to make yellow
*/
export function yellow(str: string): string {
return run(str, code([33], 39));
}
/**
* Set text color to blue.
* @param str text to make blue
*/
export function blue(str: string): string {
return run(str, code([34], 39));
}
/**
* Set text color to magenta.
* @param str text to make magenta
*/
export function magenta(str: string): string {
return run(str, code([35], 39));
}
/**
* Set text color to cyan.
* @param str text to make cyan
*/
export function cyan(str: string): string {
return run(str, code([36], 39));
}
/**
* Set text color to white.
* @param str text to make white
*/
export function white(str: string): string {
return run(str, code([37], 39));
}
/**
* Set text color to gray.
* @param str text to make gray
*/
export function gray(str: string): string {
return brightBlack(str);
}
/**
* Set text color to bright black.
* @param str text to make bright-black
*/
export function brightBlack(str: string): string {
return run(str, code([90], 39));
}
/**
* Set text color to bright red.
* @param str text to make bright-red
*/
export function brightRed(str: string): string {
return run(str, code([91], 39));
}
/**
* Set text color to bright green.
* @param str text to make bright-green
*/
export function brightGreen(str: string): string {
return run(str, code([92], 39));
}
/**
* Set text color to bright yellow.
* @param str text to make bright-yellow
*/
export function brightYellow(str: string): string {
return run(str, code([93], 39));
}
/**
* Set text color to bright blue.
* @param str text to make bright-blue
*/
export function brightBlue(str: string): string {
return run(str, code([94], 39));
}
/**
* Set text color to bright magenta.
* @param str text to make bright-magenta
*/
export function brightMagenta(str: string): string {
return run(str, code([95], 39));
}
/**
* Set text color to bright cyan.
* @param str text to make bright-cyan
*/
export function brightCyan(str: string): string {
return run(str, code([96], 39));
}
/**
* Set text color to bright white.
* @param str text to make bright-white
*/
export function brightWhite(str: string): string {
return run(str, code([97], 39));
}
/**
* Set background color to black.
* @param str text to make its background black
*/
export function bgBlack(str: string): string {
return run(str, code([40], 49));
}
/**
* Set background color to red.
* @param str text to make its background red
*/
export function bgRed(str: string): string {
return run(str, code([41], 49));
}
/**
* Set background color to green.
* @param str text to make its background green
*/
export function bgGreen(str: string): string {
return run(str, code([42], 49));
}
/**
* Set background color to yellow.
* @param str text to make its background yellow
*/
export function bgYellow(str: string): string {
return run(str, code([43], 49));
}
/**
* Set background color to blue.
* @param str text to make its background blue
*/
export function bgBlue(str: string): string {
return run(str, code([44], 49));
}
/**
* Set background color to magenta.
* @param str text to make its background magenta
*/
export function bgMagenta(str: string): string {
return run(str, code([45], 49));
}
/**
* Set background color to cyan.
* @param str text to make its background cyan
*/
export function bgCyan(str: string): string {
return run(str, code([46], 49));
}
/**
* Set background color to white.
* @param str text to make its background white
*/
export function bgWhite(str: string): string {
return run(str, code([47], 49));
}
/**
* Set background color to bright black.
* @param str text to make its background bright-black
*/
export function bgBrightBlack(str: string): string {
return run(str, code([100], 49));
}
/**
* Set background color to bright red.
* @param str text to make its background bright-red
*/
export function bgBrightRed(str: string): string {
return run(str, code([101], 49));
}
/**
* Set background color to bright green.
* @param str text to make its background bright-green
*/
export function bgBrightGreen(str: string): string {
return run(str, code([102], 49));
}
/**
* Set background color to bright yellow.
* @param str text to make its background bright-yellow
*/
export function bgBrightYellow(str: string): string {
return run(str, code([103], 49));
}
/**
* Set background color to bright blue.
* @param str text to make its background bright-blue
*/
export function bgBrightBlue(str: string): string {
return run(str, code([104], 49));
}
/**
* Set background color to bright magenta.
* @param str text to make its background bright-magenta
*/
export function bgBrightMagenta(str: string): string {
return run(str, code([105], 49));
}
/**
* Set background color to bright cyan.
* @param str text to make its background bright-cyan
*/
export function bgBrightCyan(str: string): string {
return run(str, code([106], 49));
}
/**
* Set background color to bright white.
* @param str text to make its background bright-white
*/
export function bgBrightWhite(str: string): string {
return run(str, code([107], 49));
}
/* Special Color Sequences */
/**
* Clam and truncate color codes
* @param n
* @param max number to truncate to
* @param min number to truncate from
*/
function clampAndTruncate(n: number, max = 255, min = 0): number {
return Math.trunc(Math.max(Math.min(n, max), min));
}
/**
* Set text color using paletted 8bit colors.
* https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit
* @param str text color to apply paletted 8bit colors to
* @param color code
*/
export function rgb8(str: string, color: number): string {
return run(str, code([38, 5, clampAndTruncate(color)], 39));
}
/**
* Set background color using paletted 8bit colors.
* https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit
* @param str text color to apply paletted 8bit background colors to
* @param color code
*/
export function bgRgb8(str: string, color: number): string {
return run(str, code([48, 5, clampAndTruncate(color)], 49));
}
/**
* Set text color using 24bit rgb.
* `color` can be a number in range `0x000000` to `0xffffff` or
* an `Rgb`.
*
* To produce the color magenta:
*
* ```ts
* import { rgb24 } from "./colors.ts";
* rgb24("foo", 0xff00ff);
* rgb24("foo", {r: 255, g: 0, b: 255});
* ```
* @param str text color to apply 24bit rgb to
* @param color code
*/
export function rgb24(str: string, color: number | Rgb): string {
if (typeof color === "number") {
return run(
str,
code(
[38, 2, (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff],
39,
),
);
}
return run(
str,
code(
[
38,
2,
clampAndTruncate(color.r),
clampAndTruncate(color.g),
clampAndTruncate(color.b),
],
39,
),
);
}
/**
* Set background color using 24bit rgb.
* `color` can be a number in range `0x000000` to `0xffffff` or
* an `Rgb`.
*
* To produce the color magenta:
*
* ```ts
* import { bgRgb24 } from "./colors.ts";
* bgRgb24("foo", 0xff00ff);
* bgRgb24("foo", {r: 255, g: 0, b: 255});
* ```
* @param str text color to apply 24bit rgb to
* @param color code
*/
export function bgRgb24(str: string, color: number | Rgb): string {
if (typeof color === "number") {
return run(
str,
code(
[48, 2, (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff],
49,
),
);
}
return run(
str,
code(
[
48,
2,
clampAndTruncate(color.r),
clampAndTruncate(color.g),
clampAndTruncate(color.b),
],
49,
),
);
}
// https://github.com/chalk/ansi-regex/blob/02fa893d619d3da85411acc8fd4e2eea0e95a9d9/index.js
const ANSI_PATTERN = new RegExp(
[
"[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)",
"(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))",
].join("|"),
"g",
);
/**
* Remove ANSI escape codes from the string.
* @param string to remove ANSI escape codes from
*/
export function stripColor(string: string): string {
return string.replace(ANSI_PATTERN, "");
}

228
src/lib/contract.ts Normal file
View File

@ -0,0 +1,228 @@
import { NS } from "@ns";
import { subarrayMaxSolve } from "./solve/submaxarr";
import { largestPrimeSolve } from "./solve/largestPrime";
import { spiralSolve } from "./solve/spiral";
import { arrayJumpSolve } from "./solve/arrayJump";
import { arrayJump2Solve } from "./solve/arrayJump2";
import { minimumTrianglePathSumSolve} from "./solve/minsumTriangle"
import { graph2coloringSolve } from "./solve/graph2color";
import { shortestPathInGridSolve } from "./solve/shortestPathInGrid";
import { algorithmicTradeIIISolve, algorithmicTradeIISolve, algorithmicTradeISolve, algorithmicTradeIVSolve } from "./solve/algorithmicTrader";
import {selectAllServerList} from "lib/servers";
import { logFile } from "./log";
import { SolveFailError } from "./solve/unsolved";
import { RLECompressionSolve, decompressLZSolve, compressLZSolve } from "./solve/compression";
import {caesarCipherSolve} from "./solve/caesarCipher";
export { SolveFailError } from "./solve/unsolved";
export interface ContractSolver{
name: string;
solve(n :any): string[] | number;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const ContractMAP: {[key:string]:ContractSolver} = {
/**
* A prime factor is a factor that is a prime number.
* What is the largest prime factor of the given number?
*/
"Find Largest Prime Factor": {
name: "Find Largest Prime Factor",
solve: largestPrimeSolve
},
/**
* Given the following integer array, find the contiguous subarray
* (containing at least one number) which has the largest sum and return that sum.
* 'Sum' refers to the sum of all the numbers in the subarray.
*/
"Subarray with Maximum Sum": {
name: "Subarray with Maximum Sum",
solve: subarrayMaxSolve,
},
/*"Total Ways to Sum": {
name: "Total Ways to Sum",
solve() {
notImplemented();
}
},
"Total Ways to Sum II": {
name: "Total Ways to Sum II",
solve() {
notImplemented();
}
},*/
"Spiralize Matrix": {
name: "Spiralize Matrix",
solve: spiralSolve,
},
"Array Jumping Game": {
name: "Array Jumping Game",
solve: arrayJumpSolve,
},
"Array Jumping Game II": {
name: "Array Jumping Game II",
solve: arrayJump2Solve
},/*
"Merge Overlapping Intervals": {
name: "Merge Overlapping Intervals",
solve() {
notImplemented();
}
},
"Generate IP Addresses": {
name: "Generate IP Addresses",
solve() {
notImplemented();
}
},*/
"Algorithmic Stock Trader I": {
name: "Algorithmic Stock Trader I",
solve: algorithmicTradeISolve,
},
"Algorithmic Stock Trader II": {
name: "Algorithmic Stock Trader II",
solve: algorithmicTradeIISolve,
},
"Algorithmic Stock Trader III": {
name: "Algorithmic Stock Trader III",
solve: algorithmicTradeIIISolve
},
"Algorithmic Stock Trader IV": {
name: "Algorithmic Stock Trader IV",
solve: algorithmicTradeIVSolve
},
"Minimum Path Sum in a Triangle": {
name: "Minimum Path Sum in a Triangle",
solve: minimumTrianglePathSumSolve,
},/*
"Unique Paths in a Grid I": {
name: "Unique Paths in a Grid I",
solve() {
notImplemented();
}
},
"Unique Paths in a Grid II": {
name: "Unique Paths in a Grid II",
solve() {
notImplemented();
}
},*/
"Shortest Path in a Grid": {
name: "Shortest Path in a Grid",
solve: shortestPathInGridSolve
},/*
"Sanitize Parentheses in Expression": {
name: "Sanitize Parentheses in Expression",
solve() {
notImplemented();
}
},
"Find All Valid Math Expressions": {
name: "Find All Valid Math Expressions",
solve() {
notImplemented();
}
},
"HammingCodes: Integer to Encoded Binary": {
name: "HammingCodes: Integer to Encoded Binary",
solve() {
notImplemented();
}
},
"HammingCodes: Encoded Binary to Integer": {
name: "HammingCodes: Encoded Binary to Integer",
solve() {
notImplemented();
}
},*/
"Proper 2-Coloring of a Graph": {
name: "Proper 2-Coloring of a Graph",
solve: graph2coloringSolve
},
"Compression I: RLE Compression": {
name: "Compression I: RLE Compression",
solve: RLECompressionSolve,
},
"Compression II: LZ Decompression": {
name: "Compression II: LZ Decompression",
solve: decompressLZSolve,
},
"Compression III: LZ Compression": {
name: "Compression III: LZ Compression",
solve: compressLZSolve,
},
"Encryption I: Caesar Cipher": {
name: "Encryption I: Caesar Cipher",
solve: caesarCipherSolve,
},/*
"Encryption II: Vigenère Cipher": {
name: "Encryption II: Vigenère Cipher",
solve() {
notImplemented();
}
}*/
}
export function isSolvable(ns:NS,filename: string, hostname: string): boolean {
const ct = ns.codingcontract;
const type = ct.getContractType(filename,hostname);
return (type in ContractMAP)
}
export async function solveContract(ns:NS,filename: string, hostname: string): Promise<[boolean,string]>{
const ct = ns.codingcontract;
const type = ct.getContractType(filename,hostname);
if(type in ContractMAP){
const solver = ContractMAP[type];
const data = ct.getData(filename,hostname);
let ans: number|string[];
try {
ans = solver.solve(data);
} catch (error) {
if(error instanceof SolveFailError){
ns.print(`[ERROR] failed to solve problem ${type}. throw SolveFailError`);
ns.toast(`failed to solve problem ${type}`,"error");
await logFile(ns,type+"-Fail",data);
return [false,""];
}
else{
throw error;
}
}
const reward = ct.attempt(ans,filename,hostname,{returnReward:true});
if(reward === ""){
ns.print(`[ERROR] failed to solve problem ${type}`);
ns.toast(`failed to solve problem ${type}`,"error");
await logFile(ns,type,[data,ans]);
return [false,""];
}
else {
ns.print(`success to solve.\n${reward}`);
}
return [true,reward.toString()];
}
return [false,""];
}
export interface RemoteFilePath{
hostname: string;
filename: string;
}
export function getContractList(ns: NS, hostname: string): RemoteFilePath[]{
const ctFilenameList = ns.ls(hostname,".cct").filter(x=>x.endsWith(".cct"));
return ctFilenameList.map(filename=>({
hostname: hostname,
filename: filename
}));
}
export function selectAllContract(ns: NS): RemoteFilePath[] {
const serverList = selectAllServerList(ns);
const ctList = serverList.map(server=>getContractList(ns,server.hostname))
.flat();
return ctList;
}

456
src/lib/flag.ts Normal file
View File

@ -0,0 +1,456 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
/**
* CLI flag parser.
*
* This module is browser compatible.
*
* @module
*/
import { assert } from "../util/assert";
/** The value returned from `parse`. */
export interface Args {
/** Contains all the arguments that didn't have an option associated with
* them. */
_: Array<string | number>;
// deno-lint-ignore no-explicit-any
[key: string]: any;
}
/** The options for the `parse` call. */
export interface ParseOptions {
/** When `true`, populate the result `_` with everything before the `--` and
* the result `['--']` with everything after the `--`. Here's an example:
*
* ```ts
* // $ deno run example.ts -- a arg1
* import { parse } from "./mod.ts";
* console.dir(parse(Deno.args, { "--": false }));
* // output: { _: [ "a", "arg1" ] }
* console.dir(parse(Deno.args, { "--": true }));
* // output: { _: [], --: [ "a", "arg1" ] }
* ```
*
* Defaults to `false`.
*/
"--"?: boolean;
/** An object mapping string names to strings or arrays of string argument
* names to use as aliases. */
alias?: Record<string, string | string[]>;
/** A boolean, string or array of strings to always treat as booleans. If
* `true` will treat all double hyphenated arguments without equal signs as
* `boolean` (e.g. affects `--foo`, not `-f` or `--foo=bar`) */
boolean?: boolean | string | string[];
/** An object mapping string argument names to default values. */
default?: Record<string, unknown>;
/** When `true`, populate the result `_` with everything after the first
* non-option. */
stopEarly?: boolean;
/** A string or array of strings argument names to always treat as strings. */
string?: string | string[];
/** A string or array of strings argument names to always treat as arrays.
* Collectable options can be used multiple times. All values will be
* colelcted into one array. If a non-collectable option is used multiple
* times, the last value is used. */
collect?: string | string[];
/** A string or array of strings argument names which can be negated
* by prefixing them with `--no-`, like `--no-config`. */
negatable?: string | string[];
/** A function which is invoked with a command line parameter not defined in
* the `options` configuration object. If the function returns `false`, the
* unknown option is not added to `parsedArgs`. */
unknown?: (arg: string, key?: string, value?: unknown) => unknown;
}
interface Flags {
bools: Record<string, boolean>;
strings: Record<string, boolean>;
collect: Record<string, boolean>;
negatable: Record<string, boolean>;
unknownFn: (arg: string, key?: string, value?: unknown) => unknown;
allBools: boolean;
}
interface NestedMapping {
[key: string]: NestedMapping | unknown;
}
const { hasOwn } = Object;
function get<T>(obj: Record<string, T>, key: string): T | undefined {
if (hasOwn(obj, key)) {
return obj[key];
}
return undefined;
}
function getForce<T>(obj: Record<string, T>, key: string): T {
const v = get(obj, key);
assert(v != null);
return v;
}
function isNumber(x: unknown): boolean {
if (typeof x === "number") return true;
if (/^0x[0-9a-f]+$/i.test(String(x))) return true;
return /^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/.test(String(x));
}
function hasKey(obj: NestedMapping, keys: string[]): boolean {
let o = obj;
keys.slice(0, -1).forEach((key) => {
o = (get(o, key) ?? {}) as NestedMapping;
});
const key = keys[keys.length - 1];
return key in o;
}
/** Take a set of command line arguments, optionally with a set of options, and
* return an object representing the flags found in the passed arguments.
*
* By default any arguments starting with `-` or `--` are considered boolean
* flags. If the argument name is followed by an equal sign (`=`) it is
* considered a key-value pair. Any arguments which could not be parsed are
* available in the `_` property of the returned object.
*
* ```ts
* import { parse } from "./mod.ts";
* const parsedArgs = parse(Deno.args);
* ```
*
* ```ts
* import { parse } from "./mod.ts";
* const parsedArgs = parse(["--foo", "--bar=baz", "--no-qux", "./quux.txt"]);
* // parsedArgs: { foo: true, bar: "baz", qux: false, _: ["./quux.txt"] }
* ```
*/
export function parse(
args: string[],
{
"--": doubleDash = false,
alias = {},
boolean = false,
default: defaults = {},
stopEarly = false,
string = [],
collect = [],
negatable = [],
unknown = (i: string): unknown => i,
}: ParseOptions = {},
): Args {
const flags: Flags = {
bools: {},
strings: {},
unknownFn: unknown,
allBools: false,
collect: {},
negatable: {},
};
if (boolean !== undefined) {
if (typeof boolean === "boolean") {
flags.allBools = !!boolean;
} else {
const booleanArgs = typeof boolean === "string" ? [boolean] : boolean;
for (const key of booleanArgs.filter(Boolean)) {
flags.bools[key] = true;
}
}
}
const aliases: Record<string, string[]> = {};
if (alias !== undefined) {
for (const key in alias) {
const val = getForce(alias, key);
if (typeof val === "string") {
aliases[key] = [val];
} else {
aliases[key] = val;
}
for (const alias of getForce(aliases, key)) {
aliases[alias] = [key].concat(aliases[key].filter((y) => alias !== y));
}
}
}
if (string !== undefined) {
const stringArgs = typeof string === "string" ? [string] : string;
for (const key of stringArgs.filter(Boolean)) {
flags.strings[key] = true;
const alias = get(aliases, key);
if (alias) {
for (const al of alias) {
flags.strings[al] = true;
}
}
}
}
if (collect !== undefined) {
const collectArgs = typeof collect === "string" ? [collect] : collect;
for (const key of collectArgs.filter(Boolean)) {
flags.collect[key] = true;
const alias = get(aliases, key);
if (alias) {
for (const al of alias) {
flags.collect[al] = true;
}
}
}
}
if (negatable !== undefined) {
const negatableArgs = typeof negatable === "string"
? [negatable]
: negatable;
for (const key of negatableArgs.filter(Boolean)) {
flags.negatable[key] = true;
const alias = get(aliases, key);
if (alias) {
for (const al of alias) {
flags.negatable[al] = true;
}
}
}
}
const argv: Args = { _: [] };
function argDefined(key: string, arg: string): boolean {
return (
(flags.allBools && /^--[^=]+$/.test(arg)) ||
get(flags.bools, key) ||
!!get(flags.strings, key) ||
!!get(aliases, key)
);
}
function setKey(
obj: NestedMapping,
name: string,
value: unknown,
collect = true,
): void {
let o = obj;
const keys = name.split(".");
keys.slice(0, -1).forEach(function (key): void {
if (get(o, key) === undefined) {
o[key] = {};
}
o = get(o, key) as NestedMapping;
});
const key = keys[keys.length - 1];
const collectable = collect && !!get(flags.collect, name);
if (!collectable) {
o[key] = value;
} else if (get(o, key) === undefined) {
o[key] = [value];
} else if (Array.isArray(get(o, key))) {
(o[key] as unknown[]).push(value);
} else {
o[key] = [get(o, key), value];
}
}
function setArg(
key: string,
val: unknown,
arg: string | undefined = undefined,
collect?: boolean,
): void {
if (arg && flags.unknownFn && !argDefined(key, arg)) {
if (flags.unknownFn(arg, key, val) === false) return;
}
const value = !get(flags.strings, key) && isNumber(val) ? Number(val) : val;
setKey(argv, key, value, collect);
const alias = get(aliases, key);
if (alias) {
for (const x of alias) {
setKey(argv, x, value, collect);
}
}
}
function aliasIsBoolean(key: string): boolean {
return getForce(aliases, key).some(
(x) => typeof get(flags.bools, x) === "boolean",
);
}
let notFlags: string[] = [];
// all args after "--" are not parsed
if (args.includes("--")) {
notFlags = args.slice(args.indexOf("--") + 1);
args = args.slice(0, args.indexOf("--"));
}
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (/^--.+=/.test(arg)) {
const m = arg.match(/^--([^=]+)=(.*)$/s);
assert(m != null);
const [, key, value] = m;
if (flags.bools[key]) {
const booleanValue = value !== "false";
setArg(key, booleanValue, arg);
} else {
setArg(key, value, arg);
}
} else if (
/^--no-.+/.test(arg) && get(flags.negatable, arg.replace(/^--no-/, ""))
) {
const m = arg.match(/^--no-(.+)/);
assert(m != null);
setArg(m[1], false, arg, false);
} else if (/^--.+/.test(arg)) {
const m = arg.match(/^--(.+)/);
assert(m != null);
const [, key] = m;
const next = args[i + 1];
if (
next !== undefined &&
!/^-/.test(next) &&
!get(flags.bools, key) &&
!flags.allBools &&
(get(aliases, key) ? !aliasIsBoolean(key) : true)
) {
setArg(key, next, arg);
i++;
} else if (/^(true|false)$/.test(next)) {
setArg(key, next === "true", arg);
i++;
} else {
setArg(key, get(flags.strings, key) ? "" : true, arg);
}
} else if (/^-[^-]+/.test(arg)) {
const letters = arg.slice(1, -1).split("");
let broken = false;
for (let j = 0; j < letters.length; j++) {
const next = arg.slice(j + 2);
if (next === "-") {
setArg(letters[j], next, arg);
continue;
}
if (/[A-Za-z]/.test(letters[j]) && /=/.test(next)) {
setArg(letters[j], next.split(/=(.+)/)[1], arg);
broken = true;
break;
}
if (
/[A-Za-z]/.test(letters[j]) &&
/-?\d+(\.\d*)?(e-?\d+)?$/.test(next)
) {
setArg(letters[j], next, arg);
broken = true;
break;
}
if (letters[j + 1] && letters[j + 1].match(/\W/)) {
setArg(letters[j], arg.slice(j + 2), arg);
broken = true;
break;
} else {
setArg(letters[j], get(flags.strings, letters[j]) ? "" : true, arg);
}
}
const [key] = arg.slice(-1);
if (!broken && key !== "-") {
if (
args[i + 1] &&
!/^(-|--)[^-]/.test(args[i + 1]) &&
!get(flags.bools, key) &&
(get(aliases, key) ? !aliasIsBoolean(key) : true)
) {
setArg(key, args[i + 1], arg);
i++;
} else if (args[i + 1] && /^(true|false)$/.test(args[i + 1])) {
setArg(key, args[i + 1] === "true", arg);
i++;
} else {
setArg(key, get(flags.strings, key) ? "" : true, arg);
}
}
} else {
if (!flags.unknownFn || flags.unknownFn(arg) !== false) {
argv._.push(flags.strings["_"] ?? !isNumber(arg) ? arg : Number(arg));
}
if (stopEarly) {
argv._.push(...args.slice(i + 1));
break;
}
}
}
for (const [key, value] of Object.entries(defaults)) {
if (!hasKey(argv, key.split("."))) {
setKey(argv, key, value);
if (aliases[key]) {
for (const x of aliases[key]) {
setKey(argv, x, value);
}
}
}
}
for (const key of Object.keys(flags.bools)) {
if (!hasKey(argv, key.split("."))) {
const value = get(flags.collect, key) ? [] : false;
setKey(
argv,
key,
value,
false,
);
}
}
for (const key of Object.keys(flags.strings)) {
if (!hasKey(argv, key.split(".")) && get(flags.collect, key)) {
setKey(
argv,
key,
[],
false,
);
}
}
if (doubleDash) {
argv["--"] = [];
for (const key of notFlags) {
argv["--"].push(key);
}
} else {
for (const key of notFlags) {
argv._.push(key);
}
}
return argv;
}

310
src/lib/formula.ts Normal file
View File

@ -0,0 +1,310 @@
import { NS, Server, Player, BitNodeMultipliers } from "@ns";
const CONSTANTS = {
ServerBaseGrowthRate: 1.03, // Unadjusted Growth rate
ServerMaxGrowthRate: 1.0035, // Maximum possible growth rate (max rate accounting for server security)
ServerFortifyAmount: 0.002, // Amount by which server's security increases when its hacked/grown
ServerWeakenAmount: 0.05, // Amount by which server's security decreases when weakened
}
const DefaultBitNodeMultipliers: BitNodeMultipliers = {
HackingLevelMultiplier: 1,
StrengthLevelMultiplier: 1,
DefenseLevelMultiplier: 1,
DexterityLevelMultiplier: 1,
AgilityLevelMultiplier: 1,
CharismaLevelMultiplier: 1,
ServerGrowthRate: 1,
ServerMaxMoney: 1,
ServerStartingMoney: 1,
ServerStartingSecurity: 1,
ServerWeakenRate: 1,
HomeComputerRamCost: 1,
PurchasedServerCost: 1,
PurchasedServerSoftcap: 1,
PurchasedServerLimit: 1,
PurchasedServerMaxRam: 1,
CompanyWorkMoney: 1,
CrimeMoney: 1,
HacknetNodeMoney: 1,
ManualHackMoney: 1,
ScriptHackMoney: 1,
ScriptHackMoneyGain: 1,
CodingContractMoney: 1,
ClassGymExpGain: 1,
CompanyWorkExpGain: 1,
CrimeExpGain: 1,
FactionWorkExpGain: 1,
HackExpGain: 1,
FactionPassiveRepGain: 1,
FactionWorkRepGain: 1,
RepToDonateToFaction: 1,
AugmentationMoneyCost: 1,
AugmentationRepCost: 1,
InfiltrationMoney: 1,
InfiltrationRep: 1,
FourSigmaMarketDataCost: 1,
FourSigmaMarketDataApiCost: 1,
CorporationValuation: 1,
CorporationSoftcap: 1,
BladeburnerRank: 1,
BladeburnerSkillCost: 1,
GangSoftcap: 1,
//GangUniqueAugs: 1,
DaedalusAugsRequirement: 30,
StaneksGiftPowerMultiplier: 1,
StaneksGiftExtraSize: 0,
WorldDaemonDifficulty: 1,
};
export function calculateIntelligenceBonus(intelligence: number, weight = 1): number {
return 1 + (weight * Math.pow(intelligence, 0.8)) / 600;
}
export function calculateServerGrowth(server: Server, threads: number, p: Player, cores = 1, bit: BitNodeMultipliers = DefaultBitNodeMultipliers): number {
const numServerGrowthCycles = Math.max(Math.floor(threads), 0);
//Get adjusted growth rate, which accounts for server security
const growthRate = CONSTANTS.ServerBaseGrowthRate;
let adjGrowthRate = 1 + (growthRate - 1) / server.hackDifficulty;
if (adjGrowthRate > CONSTANTS.ServerMaxGrowthRate) {
adjGrowthRate = CONSTANTS.ServerMaxGrowthRate;
}
//Calculate adjusted server growth rate based on parameters
const serverGrowthPercentage = server.serverGrowth / 100;
const numServerGrowthCyclesAdjusted =
numServerGrowthCycles * serverGrowthPercentage * bit.ServerGrowthRate;
//Apply serverGrowth for the calculated number of growth cycles
const coreBonus = 1 + (cores - 1) / 16;
return Math.pow(adjGrowthRate, numServerGrowthCyclesAdjusted * p.mults.hacking_grow * coreBonus);
}
/**
* Returns the chance the player has to successfully hack a server
*/
export function calculateHackingChance(server: Server, player: Player): number {
const hackFactor = 1.75;
const difficultyMult = (100 - server.hackDifficulty) / 100;
const skillMult = hackFactor * player.skills.hacking;
const skillChance = (skillMult - server.requiredHackingSkill) / skillMult;
const chance =
skillChance *
difficultyMult *
player.mults.hacking_chance *
calculateIntelligenceBonus(player.skills.intelligence, 1);
if (chance > 1) {
return 1;
}
if (chance < 0) {
return 0;
}
return chance;
}
/**
* Returns the amount of hacking experience the player will gain upon
* successfully hacking a server
*/
export function calculateHackingExpGain(server: Server, player: Player, bit: BitNodeMultipliers = DefaultBitNodeMultipliers): number {
const baseExpGain = 3;
const diffFactor = 0.3;
if (server.baseDifficulty == null) {
server.baseDifficulty = server.hackDifficulty;
}
let expGain = baseExpGain;
expGain += server.baseDifficulty * diffFactor;
return expGain * player.mults.hacking_exp * bit.HackExpGain;
}
/**
* Returns the percentage of money that will be stolen from a server if
* it is successfully hacked (returns the decimal form, not the actual percent value)
*/
export function calculatePercentMoneyHacked(server: Server, player: Player, bit: BitNodeMultipliers = DefaultBitNodeMultipliers): number {
// Adjust if needed for balancing. This is the divisor for the final calculation
const balanceFactor = 240;
const difficultyMult = (100 - server.hackDifficulty) / 100;
const skillMult = (player.skills.hacking - (server.requiredHackingSkill - 1)) / player.skills.hacking;
const percentMoneyHacked =
(difficultyMult * skillMult * player.mults.hacking_money * bit.ScriptHackMoney) / balanceFactor;
if (percentMoneyHacked < 0) {
return 0;
}
if (percentMoneyHacked > 1) {
return 1;
}
return percentMoneyHacked;
}
/**
* Returns time it takes to complete a hack on a server, in seconds
*/
export function calculateHackingTime(server: Server, player: Player): number {
const difficultyMult = server.requiredHackingSkill * server.hackDifficulty;
const baseDiff = 500;
const baseSkill = 50;
const diffFactor = 2.5;
let skillFactor = diffFactor * difficultyMult + baseDiff;
// tslint:disable-next-line
skillFactor /= player.skills.hacking + baseSkill;
const hackTimeMultiplier = 5;
const hackingTime =
(hackTimeMultiplier * skillFactor) /
(player.mults.hacking_speed * calculateIntelligenceBonus(player.skills.intelligence, 1));
return hackingTime;
}
/**
* Returns time it takes to complete a grow operation on a server, in seconds
*/
export function calculateGrowTime(server: Server, player: Player): number {
const growTimeMultiplier = 3.2; // Relative to hacking time. 16/5 = 3.2
return growTimeMultiplier * calculateHackingTime(server, player);
}
/**
* Returns time it takes to complete a weaken operation on a server, in seconds
*/
export function calculateWeakenTime(server: Server, player: Player): number {
const weakenTimeMultiplier = 4; // Relative to hacking time
return weakenTimeMultiplier * calculateHackingTime(server, player);
}
export function canBeAccessBitNode(ns: NS): boolean {
try {
ns.getBitNodeMultipliers();
return true;
} catch (error) {
return false;
}
}
export function getBitNodeMultipliersSafe(ns: NS): BitNodeMultipliers {
const fnName = "getBitNodeMultipliers";
const isEnabled = ns.isLogEnabled(fnName);
ns.disableLog("disableLog");
ns.disableLog("enableLog");
ns.disableLog("getBitNodeMultipliers");
try {
return ns.getBitNodeMultipliers();
} catch (error) {
if (typeof error === "string") {
return DefaultBitNodeMultipliers;
}
throw error;
} finally {
if (isEnabled) {
ns.enableLog("getBitNodeMultipliers");
}
}
}
export class FormulaHelper {
ns: NS;
constructor(ns: NS) {
this.ns = ns;
}
calculateWeakenThreadCountToTargetSecurity(targetSecurity: number, curSecurity: number, cores = 1): number {
if(curSecurity < targetSecurity) {
return 0;
}
const k = curSecurity - targetSecurity;
const c = this.ns.weakenAnalyze(1, cores);
return k / c;
}
calculateGrowThreadCount(hostname: string, currentMoney: number, targetMoney: number, cores = 1): number {
const g = this.ns.growthAnalyze(hostname, targetMoney / currentMoney, cores);
return g;
}
calculateHackSecurity(nThread: number): number {
const ServerFortifyAmount = 0.002;
return ServerFortifyAmount * nThread;
}
calculuateGrowthSecurity(nThread: number, cores = 1): number {
const ServerWeakenAmount = 0.004;
const mul = getBitNodeMultipliersSafe(this.ns);
const coreBonus = 1 + (cores - 1) / 16;
return ServerWeakenAmount * nThread * coreBonus * mul.ServerWeakenRate;
}
/**
* Returns time it takes to complete a weaken operation on a server, in milliseconds
* @param server
* @returns milliseconds to complete a weaken operation
*/
calculateWeakenTime(server: Server): number {
const player = this.ns.getPlayer();
return calculateWeakenTime(server, player) * 1000;
}
/**
* Returns time it takes to complete a grow operation on a server, in milliseconds
* @param server
* @returns milliseconds to complete a grow operation
*/
calculateGrowTime(server: Server): number {
const player = this.ns.getPlayer();
return calculateGrowTime(server, player) * 1000;
}
/**
* Returns time it takes to complete a hacking operation on a server, in milliseconds
* @param server
* @returns milliseconds to complete a hacking operation
*/
calculateHackingTime(server: Server): number {
const player = this.ns.getPlayer();
return calculateHackingTime(server, player) * 1000;
}
/**
* Hack
* @param server server to get
* @returns percent of earned money (0~1 value)
* @example
* ```ts
* calculatePercentMoneyHacked(server) //0.2 mean 20%
* ```
*/
calculatePercentMoneyHacked(server: Server): number {
const player = this.ns.getPlayer();
const bit = getBitNodeMultipliersSafe(this.ns);
return calculatePercentMoneyHacked(server, player, bit);
}
calculateServerGrowth(server: Server, thread: number, cores = 1): number {
const player = this.ns.getPlayer();
const bit = getBitNodeMultipliersSafe(this.ns);
return calculateServerGrowth(server, thread, player, cores, bit);
}
calculateHackingChance(server: Server): number {
const player = this.ns.getPlayer();
return calculateHackingChance(server, player);
}
calculateHackingExpGain(server: Server): number{
const player = this.ns.getPlayer();
const bit = getBitNodeMultipliersSafe(this.ns);
return calculateHackingExpGain(server,player,bit);
}
}

295
src/lib/fuse.esm.d.ts vendored Normal file
View File

@ -0,0 +1,295 @@
// Type definitions for Fuse.js v6.4.1
// TypeScript v3.9.5
export default Fuse
export as namespace Fuse
declare class Fuse<T> {
constructor(
list: readonly T[],
options?: Fuse.IFuseOptions<T>,
index?: FuseIndex<T>
)
/**
* Search function for the Fuse instance.
*
* ```typescript
* const list: MyType[] = [myType1, myType2, etc...]
* const options: Fuse.IFuseOptions<MyType> = {
* keys: ['key1', 'key2']
* }
*
* const myFuse = new Fuse(list, options)
* let result = myFuse.search('pattern')
* ```
*
* @param pattern The pattern to search
* @param options `Fuse.FuseSearchOptions`
* @returns An array of search results
*/
search<R = T>(
pattern: string | Fuse.Expression,
options?: Fuse.FuseSearchOptions
): Array<Fuse.FuseResult<R>>
setCollection(docs: readonly T[], index?: FuseIndex<T>): void
/**
* Adds a doc to the end the list.
*/
add(doc: T): void
/**
* Removes all documents from the list which the predicate returns truthy for,
* and returns an array of the removed docs.
* The predicate is invoked with two arguments: (doc, index).
*/
remove(predicate: (doc: T, idx: number) => boolean): T[]
/**
* Removes the doc at the specified index.
*/
removeAt(idx: number): void
/**
* Returns the generated Fuse index
*/
getIndex(): FuseIndex<T>
/**
* Return the current version.
*/
// eslint-disable-next-line @typescript-eslint/member-ordering
static version: string
/**
* Use this method to pre-generate the index from the list, and pass it
* directly into the Fuse instance.
*
* _Note that Fuse will automatically index the table if one isn't provided
* during instantiation._
*
* ```typescript
* const list: MyType[] = [myType1, myType2, etc...]
*
* const index = Fuse.createIndex<MyType>(
* keys: ['key1', 'key2']
* list: list
* )
*
* const options: Fuse.IFuseOptions<MyType> = {
* keys: ['key1', 'key2']
* }
*
* const myFuse = new Fuse(list, options, index)
* ```
* @param keys The keys to index
* @param list The list from which to create an index
* @param options?
* @returns An indexed list
*/
static createIndex<U>(
keys: Fuse.FuseOptionKey[],
list: readonly U[],
options?: Fuse.FuseIndexOptions<U>
): FuseIndex<U>
static parseIndex<U>(
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
index: any,
options?: Fuse.FuseIndexOptions<U>
): FuseIndex<U>
}
declare class FuseIndex<T> {
constructor(options?: Fuse.FuseIndexOptions<T>)
setSources(docs: readonly T[]): void
setKeys(keys: readonly string[]): void
setIndexRecords(records: Fuse.FuseIndexRecords): void
create(): void
add(doc: T): void
toJSON(): {
keys: readonly string[];
collection: Fuse.FuseIndexRecords;
}
}
declare namespace Fuse {
type FuseGetFunction<T> = (
obj: T,
path: string | string[]
) => readonly string[] | string
export type FuseIndexOptions<T> = {
getFn: FuseGetFunction<T>;
}
// {
// title: { '$': "Old Man's War" },
// 'author.firstName': { '$': 'Codenar' }
// }
//
// OR
//
// {
// tags: [
// { $: 'nonfiction', idx: 0 },
// { $: 'web development', idx: 1 },
// ]
// }
export type FuseSortFunctionItem = {
[key: string]: { $: string } | Array<{ $: string; idx: number }>;
}
// {
// score: 0.001,
// key: 'author.firstName',
// value: 'Codenar',
// indices: [ [ 0, 3 ] ]
// }
export type FuseSortFunctionMatch = {
score: number;
key: string;
value: string;
indices: Array<readonly number[]>;
}
// {
// score: 0,
// key: 'tags',
// value: 'nonfiction',
// idx: 1,
// indices: [ [ 0, 9 ] ]
// }
export type FuseSortFunctionMatchList = FuseSortFunctionMatch & {
idx: number;
}
export type FuseSortFunctionArg = {
idx: number;
item: FuseSortFunctionItem;
score: number;
matches?: Array<FuseSortFunctionMatch | FuseSortFunctionMatchList>;
}
export type FuseSortFunction = (
a: FuseSortFunctionArg,
b: FuseSortFunctionArg
) => number
// title: {
// '$': "Old Man's War",
// 'n': 0.5773502691896258
// }
type RecordEntryObject = {
v: string; // The text value
n: number; // The field-length norm
}
// 'author.tags.name': [{
// 'v': 'pizza lover',
// 'i': 2,
// 'n: 0.7071067811865475
// }
type RecordEntryArrayItem = ReadonlyArray<RecordEntryObject & { i: number }>
// TODO: this makes it difficult to infer the type. Need to think more about this
type RecordEntry = { [key: string]: RecordEntryObject | RecordEntryArrayItem }
// {
// i: 0,
// '$': {
// '0': { v: "Old Man's War", n: 0.5773502691896258 },
// '1': { v: 'Codenar', n: 1 },
// '2': [
// { v: 'pizza lover', i: 2, n: 0.7071067811865475 },
// { v: 'helo wold', i: 1, n: 0.7071067811865475 },
// { v: 'hello world', i: 0, n: 0.7071067811865475 }
// ]
// }
// }
type FuseIndexObjectRecord = {
i: number; // The index of the record in the source list
$: RecordEntry;
}
// {
// keys: null,
// list: [
// { v: 'one', i: 0, n: 1 },
// { v: 'two', i: 1, n: 1 },
// { v: 'three', i: 2, n: 1 }
// ]
// }
type FuseIndexStringRecord = {
i: number; // The index of the record in the source list
v: string; // The text value
n: number; // The field-length norm
}
type FuseIndexRecords =
| readonly FuseIndexObjectRecord[]
| readonly FuseIndexStringRecord[]
// {
// name: 'title',
// weight: 0.7
// }
export type FuseOptionKeyObject = {
name: string | string[];
weight: number;
}
export type FuseOptionKey = FuseOptionKeyObject | string | string[]
export interface IFuseOptions<T> {
isCaseSensitive?: boolean;
distance?: number;
findAllMatches?: boolean;
getFn?: FuseGetFunction<T>;
ignoreLocation?: boolean;
ignoreFieldNorm?: boolean;
includeMatches?: boolean;
includeScore?: boolean;
keys?: FuseOptionKey[];
location?: number;
minMatchCharLength?: number;
shouldSort?: boolean;
sortFn?: FuseSortFunction;
threshold?: number;
useExtendedSearch?: boolean;
}
// Denotes the start/end indices of a match
// start end
// ↓ ↓
type RangeTuple = [number, number]
export type FuseResultMatch = {
indices: readonly RangeTuple[];
key?: string;
refIndex?: number;
value?: string;
}
export type FuseSearchOptions = {
limit: number;
}
export type FuseResult<T> = {
item: T;
refIndex: number;
score?: number;
matches?: readonly FuseResultMatch[];
}
export type Expression =
| { [key: string]: string }
| {
$path: readonly string[];
$val: string;
}
| { $and?: Expression[] }
| { $or?: Expression[] }
}

1780
src/lib/fuse.esm.js Normal file

File diff suppressed because it is too large Load Diff

97
src/lib/hack.ts Normal file
View File

@ -0,0 +1,97 @@
import { NS, Server, AutocompleteData} from '@ns'
import {selectAllServerList, ServerInfo} from "./servers"
export type Hackability = "hackable" | "rooted" | "impossible"
export function getHackability(ns: NS,hostname:string): Hackability{
if (ns.hasRootAccess(hostname)) {
return "rooted";
}
if (ns.getServerRequiredHackingLevel(hostname) > ns.getHackingLevel() ||
ns.getServerNumPortsRequired(hostname) > hackablePorts(ns)) {
return "impossible";
}
return "hackable";
}
export function getHackabilityServer(ns: NS,server:Server): Hackability{
if (server.hasAdminRights) {
return "rooted";
}
if (server.requiredHackingSkill > ns.getHackingLevel() ||
server.numOpenPortsRequired > hackablePorts(ns)) {
return "impossible";
}
return "hackable";
}
export function getLockSymbol(k:Hackability):string{
switch (k) {
case "rooted":
return "🔓"
case "hackable":
return "🔐"
case "impossible":
return "🔒"
}
}
type Hack = {
file: string;
exec: (ns: NS, target:string)=> void;
};
export const hacks: Hack[] = [
{ file: 'BruteSSH.exe', exec: (ns: NS, target: string) => ns.brutessh(target) },
{ file: 'FTPCrack.exe', exec: (ns: NS, target: string) => ns.ftpcrack(target) },
{ file: 'relaySMTP.exe', exec: (ns: NS, target: string) => ns.relaysmtp(target) },
{ file: 'HTTPWorm.exe', exec: (ns: NS, target: string) => ns.httpworm(target) },
{ file: 'SQLInject.exe', exec: (ns: NS, target: string) => ns.sqlinject(target) },
]
/**
* @param {NS} ns
*/
export function hackablePorts(ns: NS): number {
function add(x:number,y:number):number{return x+y;}
return hacks.map(x=>(ns.fileExists(x.file) ? 1 : 0) as number)
.reduce(add);
}
export function isHackableServer(ns: NS, target: Server): boolean{
return target.numOpenPortsRequired <= hackablePorts(ns) &&
target.hackDifficulty <= ns.getHackingLevel()
}
export function selectHackableServerList(ns: NS): ServerInfo[]{
return selectAllServerList(ns).filter(x=>{
return isHackableServer(ns,x)
})
}
export function hackServer(ns:NS,target:Server):void;
export function hackServer(ns:NS,target:string):void;
export function hackServer(ns:NS,target:string|Server):void{
const hostname = typeof target === "string" ? target : target.hostname
hacks.forEach(x=>{
if(ns.fileExists(x.file,"home")){
x.exec(ns, hostname)
}
})
ns.nuke(hostname)
}
/** @param {NS} ns */
// eslint-disable-next-line require-await
export async function main(ns: NS):Promise<void> {
if(ns.args.length == 0){
ns.tprint("argument required")
ns.tprint("run cmd [target]")
return
}
const target = String(ns.args[0]);
hackServer(ns,target)
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function autocomplete(data : AutocompleteData, args : string[]) : string[] {
return [...data.servers]
}

14
src/lib/log.ts Normal file
View File

@ -0,0 +1,14 @@
import {NS} from "@ns"
export async function logFile(ns:NS,logFilename:string, data: unknown):Promise<void>{
logFilename = logFilename.replaceAll(" ","-");
logFilename = logFilename.replaceAll(":","-");
logFilename = `/log/${logFilename}.txt`;
const loggedData = JSON.stringify(data);
if(ns.fileExists(logFilename)){
await ns.write(logFilename,"\n"+loggedData,"a");
}
else {
await ns.write(logFilename,loggedData,"w");
}
}

778
src/lib/printf.ts Normal file
View File

@ -0,0 +1,778 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
/**
* This implementation is inspired by POSIX and Golang but does not port
* implementation code. */
enum State {
PASSTHROUGH,
PERCENT,
POSITIONAL,
PRECISION,
WIDTH,
}
enum WorP {
WIDTH,
PRECISION,
}
class Flags {
plus?: boolean;
dash?: boolean;
sharp?: boolean;
space?: boolean;
zero?: boolean;
lessthan?: boolean;
width = -1;
precision = -1;
}
const min = Math.min;
const UNICODE_REPLACEMENT_CHARACTER = "\ufffd";
const DEFAULT_PRECISION = 6;
const FLOAT_REGEXP = /(-?)(\d)\.?(\d*)e([+-])(\d+)/;
enum F {
sign = 1,
mantissa,
fractional,
esign,
exponent,
}
class Printf {
format: string;
args: unknown[];
i: number;
state: State = State.PASSTHROUGH;
verb = "";
buf = "";
argNum = 0;
flags: Flags = new Flags();
haveSeen: boolean[];
// barf, store precision and width errors for later processing ...
tmpError?: string;
constructor(format: string, ...args: unknown[]) {
this.format = format;
this.args = args;
this.haveSeen = Array.from({ length: args.length });
this.i = 0;
}
doPrintf(): string {
for (; this.i < this.format.length; ++this.i) {
const c = this.format[this.i];
switch (this.state) {
case State.PASSTHROUGH:
if (c === "%") {
this.state = State.PERCENT;
} else {
this.buf += c;
}
break;
case State.PERCENT:
if (c === "%") {
this.buf += c;
this.state = State.PASSTHROUGH;
} else {
this.handleFormat();
}
break;
default:
throw Error("Should be unreachable, certainly a bug in the lib.");
}
}
// check for unhandled args
let extras = false;
let err = "%!(EXTRA";
for (let i = 0; i !== this.haveSeen.length; ++i) {
if (!this.haveSeen[i]) {
extras = true;
err += ` '${typeof (this.args[i])}'`;
}
}
err += ")";
if (extras) {
this.buf += err;
}
return this.buf;
}
// %[<positional>]<flag>...<verb>
handleFormat(): void {
this.flags = new Flags();
const flags = this.flags;
for (; this.i < this.format.length; ++this.i) {
const c = this.format[this.i];
switch (this.state) {
case State.PERCENT:
switch (c) {
case "[":
this.handlePositional();
this.state = State.POSITIONAL;
break;
case "+":
flags.plus = true;
break;
case "<":
flags.lessthan = true;
break;
case "-":
flags.dash = true;
flags.zero = false; // only left pad zeros, dash takes precedence
break;
case "#":
flags.sharp = true;
break;
case " ":
flags.space = true;
break;
case "0":
// only left pad zeros, dash takes precedence
flags.zero = !flags.dash;
break;
default:
if (("1" <= c && c <= "9") || c === "." || c === "*") {
if (c === ".") {
this.flags.precision = 0;
this.state = State.PRECISION;
this.i++;
} else {
this.state = State.WIDTH;
}
this.handleWidthAndPrecision(flags);
} else {
this.handleVerb();
return; // always end in verb
}
} // switch c
break;
case State.POSITIONAL:
// TODO(bartlomieju): either a verb or * only verb for now
if (c === "*") {
const worp = this.flags.precision === -1
? WorP.WIDTH
: WorP.PRECISION;
this.handleWidthOrPrecisionRef(worp);
this.state = State.PERCENT;
break;
} else {
this.handleVerb();
return; // always end in verb
}
default:
throw new Error(`Should not be here ${this.state}, library bug!`);
} // switch state
}
}
/**
* Handle width or precision
* @param wOrP
*/
handleWidthOrPrecisionRef(wOrP: WorP): void {
if (this.argNum >= this.args.length) {
// handle Positional should have already taken care of it...
return;
}
const arg = this.args[this.argNum];
this.haveSeen[this.argNum] = true;
if (typeof arg === "number") {
switch (wOrP) {
case WorP.WIDTH:
this.flags.width = arg;
break;
default:
this.flags.precision = arg;
}
} else {
const tmp = wOrP === WorP.WIDTH ? "WIDTH" : "PREC";
this.tmpError = `%!(BAD ${tmp} '${this.args[this.argNum]}')`;
}
this.argNum++;
}
/**
* Handle width and precision
* @param flags
*/
handleWidthAndPrecision(flags: Flags): void {
const fmt = this.format;
for (; this.i !== this.format.length; ++this.i) {
const c = fmt[this.i];
switch (this.state) {
case State.WIDTH:
switch (c) {
case ".":
// initialize precision, %9.f -> precision=0
this.flags.precision = 0;
this.state = State.PRECISION;
break;
case "*":
this.handleWidthOrPrecisionRef(WorP.WIDTH);
// force . or flag at this point
break;
default: {
const val = parseInt(c);
// most likely parseInt does something stupid that makes
// it unusable for this scenario ...
// if we encounter a non (number|*|.) we're done with prec & wid
if (isNaN(val)) {
this.i--;
this.state = State.PERCENT;
return;
}
flags.width = flags.width == -1 ? 0 : flags.width;
flags.width *= 10;
flags.width += val;
}
} // switch c
break;
case State.PRECISION: {
if (c === "*") {
this.handleWidthOrPrecisionRef(WorP.PRECISION);
break;
}
const val = parseInt(c);
if (isNaN(val)) {
// one too far, rewind
this.i--;
this.state = State.PERCENT;
return;
}
flags.precision *= 10;
flags.precision += val;
break;
}
default:
throw new Error("can't be here. bug.");
} // switch state
}
}
/** Handle positional */
handlePositional(): void {
if (this.format[this.i] !== "[") {
// sanity only
throw new Error("Can't happen? Bug.");
}
let positional = 0;
const format = this.format;
this.i++;
let err = false;
for (; this.i !== this.format.length; ++this.i) {
if (format[this.i] === "]") {
break;
}
positional *= 10;
const val = parseInt(format[this.i]);
if (isNaN(val)) {
//throw new Error(
// `invalid character in positional: ${format}[${format[this.i]}]`
//);
this.tmpError = "%!(BAD INDEX)";
err = true;
}
positional += val;
}
if (positional - 1 >= this.args.length) {
this.tmpError = "%!(BAD INDEX)";
err = true;
}
this.argNum = err ? this.argNum : positional - 1;
return;
}
/** Handle less than */
handleLessThan(): string {
// deno-lint-ignore no-explicit-any
const arg = this.args[this.argNum] as any;
if ((arg || {}).constructor.name !== "Array") {
throw new Error(`arg ${arg} is not an array. Todo better error handling`);
}
let str = "[ ";
for (let i = 0; i !== arg.length; ++i) {
if (i !== 0) str += ", ";
str += this._handleVerb(arg[i]);
}
return str + " ]";
}
/** Handle verb */
handleVerb(): void {
const verb = this.format[this.i];
this.verb = verb;
if (this.tmpError) {
this.buf += this.tmpError;
this.tmpError = undefined;
if (this.argNum < this.haveSeen.length) {
this.haveSeen[this.argNum] = true; // keep track of used args
}
} else if (this.args.length <= this.argNum) {
this.buf += `%!(MISSING '${verb}')`;
} else {
const arg = this.args[this.argNum]; // check out of range
this.haveSeen[this.argNum] = true; // keep track of used args
if (this.flags.lessthan) {
this.buf += this.handleLessThan();
} else {
this.buf += this._handleVerb(arg);
}
}
this.argNum++; // if there is a further positional, it will reset.
this.state = State.PASSTHROUGH;
}
// deno-lint-ignore no-explicit-any
_handleVerb(arg: any): string {
switch (this.verb) {
case "t":
return this.pad(arg.toString());
case "b":
return this.fmtNumber(arg as number, 2);
case "c":
return this.fmtNumberCodePoint(arg as number);
case "d":
return this.fmtNumber(arg as number, 10);
case "o":
return this.fmtNumber(arg as number, 8);
case "x":
return this.fmtHex(arg);
case "X":
return this.fmtHex(arg, true);
case "e":
return this.fmtFloatE(arg as number);
case "E":
return this.fmtFloatE(arg as number, true);
case "f":
case "F":
return this.fmtFloatF(arg as number);
case "g":
return this.fmtFloatG(arg as number);
case "G":
return this.fmtFloatG(arg as number, true);
case "s":
return this.fmtString(arg as string);
case "T":
return this.fmtString(typeof arg);
case "v":
return this.fmtV(arg);
case "j":
return this.fmtJ(arg);
default:
return `%!(BAD VERB '${this.verb}')`;
}
}
/**
* Pad a string
* @param s text to pad
*/
pad(s: string): string {
const padding = this.flags.zero ? "0" : " ";
if (this.flags.dash) {
return s.padEnd(this.flags.width, padding);
}
return s.padStart(this.flags.width, padding);
}
/**
* Pad a number
* @param nStr
* @param neg
*/
padNum(nStr: string, neg: boolean): string {
let sign: string;
if (neg) {
sign = "-";
} else if (this.flags.plus || this.flags.space) {
sign = this.flags.plus ? "+" : " ";
} else {
sign = "";
}
const zero = this.flags.zero;
if (!zero) {
// sign comes in front of padding when padding w/ zero,
// in from of value if padding with spaces.
nStr = sign + nStr;
}
const pad = zero ? "0" : " ";
const len = zero ? this.flags.width - sign.length : this.flags.width;
if (this.flags.dash) {
nStr = nStr.padEnd(len, pad);
} else {
nStr = nStr.padStart(len, pad);
}
if (zero) {
// see above
nStr = sign + nStr;
}
return nStr;
}
/**
* Format a number
* @param n
* @param radix
* @param upcase
*/
fmtNumber(n: number, radix: number, upcase = false): string {
let num = Math.abs(n).toString(radix);
const prec = this.flags.precision;
if (prec !== -1) {
this.flags.zero = false;
num = n === 0 && prec === 0 ? "" : num;
while (num.length < prec) {
num = "0" + num;
}
}
let prefix = "";
if (this.flags.sharp) {
switch (radix) {
case 2:
prefix += "0b";
break;
case 8:
// don't annotate octal 0 with 0...
prefix += num.startsWith("0") ? "" : "0";
break;
case 16:
prefix += "0x";
break;
default:
throw new Error("cannot handle base: " + radix);
}
}
// don't add prefix in front of value truncated by precision=0, val=0
num = num.length === 0 ? num : prefix + num;
if (upcase) {
num = num.toUpperCase();
}
return this.padNum(num, n < 0);
}
/**
* Format number with code points
* @param n
*/
fmtNumberCodePoint(n: number): string {
let s = "";
try {
s = String.fromCodePoint(n);
} catch {
s = UNICODE_REPLACEMENT_CHARACTER;
}
return this.pad(s);
}
/**
* Format special float
* @param n
*/
fmtFloatSpecial(n: number): string {
// formatting of NaN and Inf are pants-on-head
// stupid and more or less arbitrary.
if (isNaN(n)) {
this.flags.zero = false;
return this.padNum("NaN", false);
}
if (n === Number.POSITIVE_INFINITY) {
this.flags.zero = false;
this.flags.plus = true;
return this.padNum("Inf", false);
}
if (n === Number.NEGATIVE_INFINITY) {
this.flags.zero = false;
return this.padNum("Inf", true);
}
return "";
}
/**
* Round fraction to precision
* @param fractional
* @param precision
* @returns tuple of fractional and round
*/
roundFractionToPrecision(
fractional: string,
precision: number,
): [string, boolean] {
let round = false;
if (fractional.length > precision) {
fractional = "1" + fractional; // prepend a 1 in case of leading 0
let tmp = parseInt(fractional.substr(0, precision + 2)) / 10;
tmp = Math.round(tmp);
fractional = Math.floor(tmp).toString();
round = fractional[0] === "2";
fractional = fractional.substr(1); // remove extra 1
} else {
while (fractional.length < precision) {
fractional += "0";
}
}
return [fractional, round];
}
/**
* Format float E
* @param n
* @param upcase
*/
fmtFloatE(n: number, upcase = false): string {
const special = this.fmtFloatSpecial(n);
if (special !== "") {
return special;
}
const m = n.toExponential().match(FLOAT_REGEXP);
if (!m) {
throw Error("can't happen, bug");
}
let fractional = m[F.fractional];
const precision = this.flags.precision !== -1
? this.flags.precision
: DEFAULT_PRECISION;
let rounding = false;
[fractional, rounding] = this.roundFractionToPrecision(
fractional,
precision,
);
let e = m[F.exponent];
let esign = m[F.esign];
// scientific notation output with exponent padded to minlen 2
let mantissa = parseInt(m[F.mantissa]);
if (rounding) {
mantissa += 1;
if (10 <= mantissa) {
mantissa = 1;
const r = parseInt(esign + e) + 1;
e = r.toString();
esign = r < 0 ? "-" : "+";
}
}
e = e.length == 1 ? "0" + e : e;
const val = `${mantissa}.${fractional}${upcase ? "E" : "e"}${esign}${e}`;
return this.padNum(val, n < 0);
}
/**
* Format float F
* @param n
*/
fmtFloatF(n: number): string {
const special = this.fmtFloatSpecial(n);
if (special !== "") {
return special;
}
// stupid helper that turns a number into a (potentially)
// VERY long string.
function expandNumber(n: number): string {
if (Number.isSafeInteger(n)) {
return n.toString() + ".";
}
const t = n.toExponential().split("e");
let m = t[0].replace(".", "");
const e = parseInt(t[1]);
if (e < 0) {
let nStr = "0.";
for (let i = 0; i !== Math.abs(e) - 1; ++i) {
nStr += "0";
}
return (nStr += m);
} else {
const splIdx = e + 1;
while (m.length < splIdx) {
m += "0";
}
return m.substr(0, splIdx) + "." + m.substr(splIdx);
}
}
// avoiding sign makes padding easier
const val = expandNumber(Math.abs(n)) as string;
const arr = val.split(".");
let dig = arr[0];
let fractional = arr[1];
const precision = this.flags.precision !== -1
? this.flags.precision
: DEFAULT_PRECISION;
let round = false;
[fractional, round] = this.roundFractionToPrecision(fractional, precision);
if (round) {
dig = (parseInt(dig) + 1).toString();
}
return this.padNum(`${dig}.${fractional}`, n < 0);
}
/**
* Format float G
* @param n
* @param upcase
*/
fmtFloatG(n: number, upcase = false): string {
const special = this.fmtFloatSpecial(n);
if (special !== "") {
return special;
}
// The double argument representing a floating-point number shall be
// converted in the style f or e (or in the style F or E in
// the case of a G conversion specifier), depending on the
// value converted and the precision. Let P equal the
// precision if non-zero, 6 if the precision is omitted, or 1
// if the precision is zero. Then, if a conversion with style E would
// have an exponent of X:
// - If P > X>=-4, the conversion shall be with style f (or F )
// and precision P -( X+1).
// - Otherwise, the conversion shall be with style e (or E )
// and precision P -1.
// Finally, unless the '#' flag is used, any trailing zeros shall be
// removed from the fractional portion of the result and the
// decimal-point character shall be removed if there is no
// fractional portion remaining.
// A double argument representing an infinity or NaN shall be
// converted in the style of an f or F conversion specifier.
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/fprintf.html
let P = this.flags.precision !== -1
? this.flags.precision
: DEFAULT_PRECISION;
P = P === 0 ? 1 : P;
const m = n.toExponential().match(FLOAT_REGEXP);
if (!m) {
throw Error("can't happen");
}
const X = parseInt(m[F.exponent]) * (m[F.esign] === "-" ? -1 : 1);
let nStr = "";
if (P > X && X >= -4) {
this.flags.precision = P - (X + 1);
nStr = this.fmtFloatF(n);
if (!this.flags.sharp) {
nStr = nStr.replace(/\.?0*$/, "");
}
} else {
this.flags.precision = P - 1;
nStr = this.fmtFloatE(n);
if (!this.flags.sharp) {
nStr = nStr.replace(/\.?0*e/, upcase ? "E" : "e");
}
}
return nStr;
}
/**
* Format string
* @param s
*/
fmtString(s: string): string {
if (this.flags.precision !== -1) {
s = s.substr(0, this.flags.precision);
}
return this.pad(s);
}
/**
* Format hex
* @param val
* @param upper
*/
fmtHex(val: string | number, upper = false): string {
// allow others types ?
switch (typeof val) {
case "number":
return this.fmtNumber(val as number, 16, upper);
case "string": {
const sharp = this.flags.sharp && val.length !== 0;
let hex = sharp ? "0x" : "";
const prec = this.flags.precision;
const end = prec !== -1 ? min(prec, val.length) : val.length;
for (let i = 0; i !== end; ++i) {
if (i !== 0 && this.flags.space) {
hex += sharp ? " 0x" : " ";
}
// TODO(bartlomieju): for now only taking into account the
// lower half of the codePoint, ie. as if a string
// is a list of 8bit values instead of UCS2 runes
const c = (val.charCodeAt(i) & 0xff).toString(16);
hex += c.length === 1 ? `0${c}` : c;
}
if (upper) {
hex = hex.toUpperCase();
}
return this.pad(hex);
}
default:
throw new Error(
"currently only number and string are implemented for hex",
);
}
}
/**
* Format value
* @param val
*/
fmtV(val: Record<string, unknown>): string {
if (this.flags.sharp) {
const options = this.flags.precision !== -1
? { depth: this.flags.precision }
: {};
return this.pad(val.toString());
} else {
const p = this.flags.precision;
return p === -1 ? val.toString() : val.toString().substr(0, p);
}
}
/**
* Format JSON
* @param val
*/
fmtJ(val: unknown): string {
return JSON.stringify(val);
}
}
/**
* Converts and format a variable number of `args` as is specified by `format`.
* `sprintf` returns the formatted string.
*
* @param format
* @param args
*/
export function sprintf(format: string, ...args: unknown[]): string {
const printf = new Printf(format, ...args);
return printf.doPrintf();
}
import {NS} from "@ns";
/**
* Converts and format a variable number of `args` as is specified by `format`.
* `printf` writes the formatted string to standard output.
* @param format
* @param args
*/
export function printf(ns: NS, format: string, ...args: unknown[]): void {
const s = sprintf(format, ...args);
ns.tprint(s);
}

185
src/lib/scheduler.ts Normal file
View File

@ -0,0 +1,185 @@
import {NS} from "@ns";
/**
* tick in scheduler
*/
export interface Tickable {
/**
* tick in scheduler
*/
tick(): void;
/**
* no work to run in this tick.
*/
empty(): boolean;
}
/**
* worker in scheduler
*/
export interface Work {
work: (() => Promise<void>) | (()=>void);
}
/**
* set of work to be executed at the same time
*/
export class WorkSet implements Work{
works: Work[];
constructor(){
this.works = [];
}
add(work: Work): void{
this.works.push(work);
}
remove(work: Work): void{
this.works = this.works.filter(x=>x !== work);
}
concat(other: WorkSet): void{
this.works = this.works.concat(other.works);
}
async work(): Promise<void>{
for(const work of this.works){
await work.work();
}
}
}
export function mergeWork(...args: Work[]): WorkSet{
const workSet = new WorkSet();
for(const work of args){
if(work instanceof WorkSet){
workSet.concat(work);
}
else {
workSet.add(work);
}
}
return workSet;
}
class PidTerminateExecutor implements Tickable{
ns: NS;
lastCheck: number;
pidSet: Map<number, Work>;
constructor(ns: NS){
this.ns = ns;
this.lastCheck = Date.now();
this.pidSet = new Map();
}
watchPid(pid: number, work: Work): void{
if(this.pidSet.has(pid)){
throw new Error(`pid ${pid} is already watched`);
}
this.pidSet.set(pid, work);
}
empty(): boolean{
return this.pidSet.size === 0;
}
async tick(): Promise<void>{
const current = Date.now();
const deadScripts = this.ns.getRecentScripts().filter(x=>x.timeOfDeath.getTime() >= this.lastCheck);
for(const script of deadScripts){
const target = this.pidSet.get(script.pid);
if(target){
await target.work();
}
this.pidSet.delete(script.pid);
}
this.lastCheck = current;
}
}
function normalizeTime(time: number, timeResolution: number): number{
return Math.floor(time / timeResolution) * timeResolution;
}
class TimerExecutor implements Tickable{
ns: NS;
timerResolution: number;
timerStart: number;
workBucket: Map<number, WorkSet>;
constructor(ns: NS, timerResolution = 250){
this.ns = ns;
this.workBucket = new Map;
this.timerResolution = timerResolution;
this.timerStart = performance.now();
}
empty(): boolean{
return this.workBucket.size === 0;
}
setTimeout(work: Work, delay: number): void {
const time = performance.now() + delay - this.timerStart;
const normalizedTime = normalizeTime(time, this.timerResolution);
const workSet = this.workBucket.get(normalizedTime);
if(workSet){
workSet.add(work);
}
else{
this.workBucket.set(normalizedTime, mergeWork(work));
}
}
async tick(): Promise<void> {
const current = performance.now();
const normalizedCurrent = normalizeTime(current-this.timerStart, this.timerResolution);
const workSet = this.workBucket.get(normalizedCurrent);
if(workSet){
await workSet.work();
this.workBucket.delete(normalizedCurrent);
}
}
async sleepForTick(): Promise<void>{
const current = performance.now();
const normalizedCurrent = normalizeTime(current-this.timerStart, this.timerResolution);
const next = normalizedCurrent + this.timerResolution;
const nextTime = this.timerStart + next;
const sleepTime = nextTime - current;
//this.ns.print(`Sleeping for ${sleepTime}ms`);
await this.ns.sleep(sleepTime);
}
}
export class Scheduler{
ns: NS;
timeResolution: number;
pidWatcher: PidTerminateExecutor;
timerWatcher: TimerExecutor;
constructor(ns: NS, timeResolution = 250){
this.ns = ns;
this.timeResolution = timeResolution;
this.pidWatcher = new PidTerminateExecutor(ns);
this.timerWatcher = new TimerExecutor(ns, timeResolution);
}
empty(): boolean{
return this.pidWatcher.empty() && this.timerWatcher.empty();
}
setTimeout(work: Work, delay: number): void {
this.timerWatcher.setTimeout(work, delay);
}
sleepInWork(delay: number): Promise<void>{
return new Promise<void>(resolve=>{
this.setTimeout({work(){
resolve();
} }, delay);
});
}
onPidTerminate(pid: number, work: Work): void{
this.pidWatcher.watchPid(pid, work);
}
async tick(): Promise<void>{
await this.pidWatcher.tick();
await this.timerWatcher.tick();
await this.timerWatcher.sleepForTick();
}
}

72
src/lib/servers.ts Normal file
View File

@ -0,0 +1,72 @@
import { NS, Server } from '@ns'
/** @param {NS} ns */
export async function main(ns: NS): Promise<void> {
if (ns.args.length == 0) {
ns.tprint("argument required")
ns.tprint("search all server and print to json")
ns.tprint("run cmd [filePath]")
ns.exit()
}
const filePath = String(ns.args[0])
const collectedData = selectAllServerList(ns)
const buf = JSON.stringify(collectedData, undefined, 2)
await ns.write(filePath, buf, "w")
}
export interface ServerInfo extends Server{
relHost:string[];
}
/**
* @param {NS} ns
* @param func
*/
export function visitAllServerList(ns: NS, func:(data: ServerInfo)=>void):void {
/** @type {string[]} */
const queue: string[] = ["home"]
/** @type {Set<string>} */
const visited = new Set()
//breadth first search
while (queue.length > 0) {
const vHost = queue.pop()
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const data = getServersInfo(ns, vHost!)
func(data)
visited.add(vHost)
for (const h of data.relHost) {
if (!visited.has(h)) {
queue.push(h)
}
}
}
}
/**@param {NS} ns */
export function selectAllServerList(ns: NS): ServerInfo[] {
const collectedData: ServerInfo[] = []
visitAllServerList(ns, (data) => {
collectedData.push(data)
})
return collectedData
}
export function isRootedServer(server:Server):boolean{
return server.hasAdminRights
}
export function selectRootedServerList(ns:NS): ServerInfo[]{
return selectAllServerList(ns).filter(isRootedServer)
}
/** @param {NS} ns
* @param {string} host
* @return {Server & {relHost: string[]}}
*/
export function getServersInfo(ns: NS, host: string): ServerInfo {
const neighbor = ns.scan(host)
const info = ns.getServer(host)
return { ...info, relHost: neighbor }
}

View File

@ -0,0 +1,117 @@
export function algorithmicTradeISolve(arr:number[]):number{
const localMax = arr.map((v,i,ar)=>{
return Math.max(...ar.slice(i)) - v;
});
//console.log(localMax)
return Math.max(...localMax, 0);
}
//console.log(algorithmicTrade1Solve([130,188,29,178,176,54,157,177,151,184,105,22,185,165,151,80,58,25]))
//163
export function algorithmicTradeIISolve(arr:number[]):number{
const memo: number[][] = (new Array(arr.length)).fill([])
.map(_=>new Array(arr.length).fill(-1));
const v = solve(arr,0,0);
return v;
function solve(arr:number[],i: number,sellChance: number):number{
if(arr.length == i){
return 0;
}
if(memo[i][sellChance] >= 0){
return memo[i][sellChance];
}
const result = [];
if(sellChance == 1){
const sell = arr[i];
result.push(sell + solve(arr,i+1,0));
}
else {
const buy = arr[i];
result.push(-buy + solve(arr,i+1, 1));
}
result.push(solve(arr,i+1,sellChance));
const v = Math.max(...result);
memo[i][sellChance] = v;
return v;
}
}
//console.log(algorithmicTrade2Solve([17,172,46,80,159,16,159,182,60,27,114,75,176,106,5,30,130,87,22]))
//747
export function algorithmicTradeIIISolve(arr:number[]):number{
const memo: number[][] = (new Array(arr.length)).fill([])
.map(_=>new Array(5).fill(-1));
const v = solve(arr,0,4);
console.log(memo)
return v;
function solve(arr:number[],i: number,transaction: number):number{
if(arr.length == i){
return 0;
}
if(transaction == 0){
return 0;
}
if(memo[i][transaction] >= 0){
return memo[i][transaction];
}
const result = [];
const isBuy = transaction % 2 === 0;
if(isBuy){
const buy = arr[i];
result.push(-buy + solve(arr,i+1, transaction - 1));
}
else {
const sell = arr[i];
result.push(sell + solve(arr,i+1, transaction - 1));
}
result.push(solve(arr,i+1,transaction));
const v = Math.max(...result);
memo[i][transaction] = v;
return v;
}
}
//console.log(algorithmicTradeIIISolve([25,166,22,187,140,184,118,71,36,43,127,68,94,133,141]))
//306
export function algorithmicTradeIVSolve([transaction,arr]:[number,number[]]):number{
const memo: number[][] = (new Array(arr.length)).fill([])
.map(_=>new Array(transaction * 2 + 4).fill(-1));
const v = solve(arr,0,transaction * 2);
console.log(memo)
return v;
function solve(arr:number[],i: number,transaction: number):number{
if(arr.length == i){
return 0;
}
if(transaction == 0){
return 0;
}
if(memo[i][transaction - 1] >= 0){
return memo[i][transaction - 1];
}
const result = [];
const isBuy = transaction % 2 === 0;
if(isBuy){
const buy = arr[i];
result.push(-buy + solve(arr,i+1, transaction - 1));
}
else {
const sell = arr[i];
result.push(sell + solve(arr,i+1, transaction - 1));
}
result.push(solve(arr,i+1,transaction));
const v = Math.max(...result);
memo[i][transaction - 1] = v;
return v;
}
}
//const v = algorithmicTradeIVSolve([7, [86,97,78,46,113,13,79,42,153,96,93,116,64,190,5,76,66,121,149,177,56,33,75,107,99,106,117]])
//console.log(v);
//649

View File

@ -0,0 +1,30 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
//Array Jumping Game
export function isJumpPossible(arr:number[]){
let possible = 0;
for (let i = 0; i <= possible && i < arr.length; i++) {
const element = arr[i];
possible = Math.max(possible,i+element);
if(possible >= arr.length - 1) return true;
}
return false;
}
export function arrayJumpSolve(arr:number[]): number{
return isJumpPossible(arr) ? 1 : 0;
}
export const testcases:Array<[number[],string]> = [
[[5,4,7,0,0,0,0],"1"],
[[0,0,0,5,7],"0"],
[[1,1,1,1],"1"],
[[3,0,0,1],"1"],
[[2,0,1,0],"1"],
[[2,0,1,0,0],"0"]
]
//for (const [data,ans] of testcases) {
// console.log(solve(data), ans);
//}

View File

@ -0,0 +1,31 @@
export function arrayJump2(arr:number[]): number{
const d = new Array(arr.length).fill(Infinity);
d[0] = 0;
for (let i = 0; i < arr.length; i++) {
const element = arr[i];
for (let j = i+1; j <= element+i && j < arr.length; j++) {
d[j] = Math.min(d[j],d[i]+1);
}
}
return d[arr.length - 1];
}
export function arrayJump2Solve(arr:number[]): number{
const v = arrayJump2(arr);
if (v === Infinity) {
return 0;
}
return v;
}
export const testcases: Array<[number[],number]> = [
[[1,4,2,5,7,2,2,2,4,4,5,5,3,6,2,6,4,4,1,3], 5],
[[1,1,1,1],3],
[[3,1,1,1],1],
]
//for (const testcase of testcases) {
// const [data,ans] = testcase;
// const step = arrayJump2(data);
// console.log(step,ans)
//}

View File

@ -0,0 +1,30 @@
const alphabet = 'abcdefghijklmnopqrstuvwxyz'.toUpperCase();
export function caesarCipher(str: string, key: number): string {
return str.split('').map((ch) => {
if (ch === ' ') return ch;
const idx = alphabet.indexOf(ch);
return alphabet[(idx + key) % alphabet.length];
}).join('');
}
type CaesarCipherInput = [string, number];
export function caesarCipherSolve([plain, key]: CaesarCipherInput): string[] {
return [caesarCipher(plain, 26-key)];
}
//type TestCase = [CaesarCipherInput, string];
//
//const TestCases: TestCase[] = [
// [["ABC", -1], "BCD"],
// [["ABC", -2], "CDE"],
// [["ABC", -3], "DEF"],
// [["ABC", -4], "EFG"],
// [["ABC", -5], "FGH"],
// [["ABC", -6], "GHI"],
// [["HELLO WORLD", -1], "IFMMP XPSME"],
// [["Z", 1], "A"],
// [["MODEM FRAME VIRUS TRASH POPUP", -13], "ZBQRZ SENZR IVEHF GENFU CBCHC"]
// [["LOGIN MOUSE TABLE PRINT FLASH", -18], "TWOQV UWCAM BIJTM XZQVB NTIAP"]
//]

View File

@ -0,0 +1,154 @@
/**
* Compression I: RLE Compression
* Run-length encoding (RLE) is a data compression technique which encodes data as
* a series of runs of a repeated single character. Runs are encoded as a length,
* followed by the character itself. Lengths are encoded as a single ASCII digit;
* runs of 10 characters or more are encoded by splitting them into multiple runs.
*
* You are given the following input string:
* iiiiiii225RHHuu2222fddgxxxxxxxxxxxxVVVVVVVVVVVVVooooNNDDDDDDDDDGGgqqpSSSy3FFFFFF
* Encode it using run-length encoding with the minimum possible output length.
*
* Examples:
* aaaaabccc -> 5a1b3c
* aAaAaA -> 1a1A1a1A1a1A
* 111112333 -> 511233
* zzzzzzzzzzzzzzzzzzz -> 9z9z1z (or 9z8z2z, etc.)
*/
function matchLength(str: string, start: number): number {
const char = str.charAt(start);
let i;
for (i = 0; start + i < str.length; i++) {
const element = str.charAt(i + start);
if (element !== char) {
return i;
}
}
return i;
}
export function* RLECompression(str: string): Generator<[string, number]> {
let i = 0;
while (i < str.length) {
const char = str[i];
let v = matchLength(str, i);
while (v > 9) {
yield [char, 9];
v -= 9;
i += 9;
}
yield [char, v];
i += v;
}
return;
}
export function RLECompressionSolve(data: string): string[] {
return [[...RLECompression(data)].map(([ch, v]) => `${v}${ch}`).join("")]
}
//const RLETestCase = [
//["aaaaabccc", "5a1b3c"],
//["aAaAaA", "1a1A1a1A1a1A"],
//["111112333", "511233"],
//["zzzzzzzzzzzzzzzzzzz", "9z9z1z"],
//]
//for (const [data,ans] of RLETestCase) {
// const predict = [...RLECompression(data)].map(([ch,v])=>`${v}${ch}`).join("")
// console.log(predict,ans);
//}
export function decompressLZSolve(data: string): string[] {
return [decompressLZ(data)];
}
// code from
// https://github.com/mirkoconsiglio/Bitburner-scripts/blob/master/contracts/contractor.js
//
function decompressLZ(str: string): string {
let decoded = '', type = 0, len, ref, pos, i = 0, j;
while (i < str.length) {
if (i > 0) type ^= 1;
len = parseInt(str[i]);
ref = parseInt(str[++i]);
if (len === 0) continue;
if (!isNaN(ref) && type === 1) {
i++;
for (j = 0; j < len; j++) decoded += decoded[decoded.length - ref];
} else {
pos = i;
for (; i < len + pos; i++) decoded += str[i];
}
}
return decoded;
}
export function compressLZSolve(str: string): string[] {
return [compressLZ(str)];
}
export function compressLZ(str: string): string {
// state [i][j] contains a backreference of offset i and length j
let cur_state = Array.from(Array(10), _ => Array(10)), new_state, tmp_state, result;
cur_state[0][1] = ''; // initial state is a literal of length 1
for (let i = 1; i < str.length; i++) {
new_state = Array.from(Array(10), _ => Array(10));
const c = str[i];
// handle literals
for (let len = 1; len <= 9; len++) {
const input = cur_state[0][len];
if (input === undefined) continue;
if (len < 9) set(new_state, 0, len + 1, input); // extend current literal
else set(new_state, 0, 1, input + '9' + str.substring(i - 9, i) + '0'); // start new literal
for (let offset = 1; offset <= Math.min(9, i); offset++) { // start new backreference
if (str[i - offset] === c) set(new_state, offset, 1, input + len + str.substring(i - len, i));
}
}
// handle backreferences
for (let offset = 1; offset <= 9; offset++) {
for (let len = 1; len <= 9; len++) {
const input = cur_state[offset][len];
if (input === undefined) continue;
if (str[i - offset] === c) {
if (len < 9) set(new_state, offset, len + 1, input); // extend current backreference
else set(new_state, offset, 1, input + '9' + offset + '0'); // start new backreference
}
set(new_state, 0, 1, input + len + offset); // start new literal
// end current backreference and start new backreference
for (let new_offset = 1; new_offset <= Math.min(9, i); new_offset++) {
if (str[i - new_offset] === c) set(new_state, new_offset, 1, input + len + offset + '0');
}
}
}
tmp_state = new_state;
new_state = cur_state;
cur_state = tmp_state;
}
for (let len = 1; len <= 9; len++) {
let input = cur_state[0][len];
if (input === undefined) continue;
input += len + str.substring(str.length - len, str.length);
// noinspection JSUnusedAssignment
if (result === undefined || input.length < result.length) result = input;
}
for (let offset = 1; offset <= 9; offset++) {
for (let len = 1; len <= 9; len++) {
let input = cur_state[offset][len];
if (input === undefined) continue;
input += len + '' + offset;
if (result === undefined || input.length < result.length) result = input;
}
}
return result ?? '';
/**
*
* @param {string[][]} state
* @param {number} i
* @param {number} j
* @param {string} str
*/
function set(state: string[][], i:number, j:number, str:string): void {
if (state[i][j] === undefined || str.length < state[i][j].length) state[i][j] = str;
}
}

View File

View File

@ -0,0 +1,86 @@
//
// You are given the following data, representing a graph:
// [6,[[0,3],[0,4],[4,5],[0,1],[1,2],[1,4]]]
// Note that "graph", as used here, refers to the field of graph theory,
// and has no relation to statistics or plotting. The first element of
// the data represents the number of vertices in the graph. Each vertex
// is a unique number between 0 and 5. The next element of the data represents
// the edges of the graph. Two vertices u,v in a graph are said to be adjacent
// if there exists an edge [u,v]. Note that an edge [u,v] is the same as
// an edge [v,u], as order does not matter. You must construct a 2-coloring
// of the graph, meaning that you have to assign each vertex in the graph
// a "color", either 0 or 1, such that no two adjacent vertices have the
// same color. Submit your answer in the form of an array, where element i
// represents the color of vertex i. If it is impossible to construct a
// 2-coloring of the given graph, instead submit an empty array.
//
/**
* input
*/
type Input = [number,
Array<[number,number]>
];
export function graph2coloring([nVertex,edges]:Input):number[]{
const colors = new Array(nVertex).fill(null);
const queue = [0];
colors[0] = false;
while(queue.length > 0){
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const elem = queue.pop()!;
const neighbors = getNeighborhood(elem);
const chcolor = !colors[elem];
for (const v of neighbors) {
//propagate color;
if(colors[v] === null){
colors[v] = chcolor;
queue.push(v);
}
else if(colors[v] == !chcolor){
return [];
}
}
}
return colors.map(x=> x ? 1 : 0);
function getNeighborhood(i: number): number[]{
return edges.filter(([a,b])=> a==i || b==i)
.map(([a,b])=> a == i ? b : a);
}
}
export function graph2coloringSolve(input:Input):string[]{
const ret = graph2coloring(input).map(x=>x.toString());
if (ret.length === 0) {
return ["[]"];
}
return ret;
}
export type TestCase = [
Input,
number[]
]
export const testcases:TestCase[] =
[
[
[4, [[0, 2], [0, 3], [1, 2], [1, 3]]],
[0, 0, 1, 1]
],
[
[3, [[0, 1], [0, 2], [1, 2]]],
[]
],
[
[6,[[0,3],[0,4],[4,5],[0,1],[1,2],[1,4]]],
[]
],
[
[9,[[1,4],[3,6],[5,8],[0,1],[7,8],[2,8],[1,8],[2,6],[1,2],[4,6],[4,8]]],
[]
]
]
//for (const [data,ans] of testcases) {
// console.log(graph2coloringSolve(data), ans)
//}

View File

@ -0,0 +1,278 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
const primeTable = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29,
31, 37, 41, 43, 47, 53, 59, 61, 67, 71,
73, 79, 83, 89, 97, 101, 103, 107, 109, 113,
127, 131, 137, 139, 149, 151, 157, 163, 167, 173,
179, 181, 191, 193, 197, 199, 211, 223, 227, 229,
233, 239, 241, 251, 257, 263, 269, 271, 277, 281,
283, 293, 307, 311, 313, 317, 331, 337, 347, 349,
353, 359, 367, 373, 379, 383, 389, 397, 401, 409,
419, 421, 431, 433, 439, 443, 449, 457, 461, 463,
467, 479, 487, 491, 499, 503, 509, 521, 523, 541,
547, 557, 563, 569, 571, 577, 587, 593, 599, 601,
607, 613, 617, 619, 631, 641, 643, 647, 653, 659,
661, 673, 677, 683, 691, 701, 709, 719, 727, 733,
739, 743, 751, 757, 761, 769, 773, 787, 797, 809,
811, 821, 823, 827, 829, 839, 853, 857, 859, 863,
877, 881, 883, 887, 907, 911, 919, 929, 937, 941,
947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013,
1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069,
1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151,
1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223,
1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291,
1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373,
1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451,
1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511,
1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583,
1597, 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657,
1663, 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733,
1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811,
1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889,
1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987,
1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053,
2063, 2069, 2081, 2083, 2087, 2089, 2099, 2111, 2113, 2129,
2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203, 2207, 2213,
2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287,
2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357,
2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423,
2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531,
2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617,
2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687,
2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741,
2749, 2753, 2767, 2777, 2789, 2791, 2797, 2801, 2803, 2819,
2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, 2903,
2909, 2917, 2927, 2939, 2953, 2957, 2963, 2969, 2971, 2999,
3001, 3011, 3019, 3023, 3037, 3041, 3049, 3061, 3067, 3079,
3083, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, 3181,
3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, 3257,
3259, 3271, 3299, 3301, 3307, 3313, 3319, 3323, 3329, 3331,
3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413,
3433, 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511,
3517, 3527, 3529, 3533, 3539, 3541, 3547, 3557, 3559, 3571,
3581, 3583, 3593, 3607, 3613, 3617, 3623, 3631, 3637, 3643,
3659, 3671, 3673, 3677, 3691, 3697, 3701, 3709, 3719, 3727,
3733, 3739, 3761, 3767, 3769, 3779, 3793, 3797, 3803, 3821,
3823, 3833, 3847, 3851, 3853, 3863, 3877, 3881, 3889, 3907,
3911, 3917, 3919, 3923, 3929, 3931, 3943, 3947, 3967, 3989,
4001, 4003, 4007, 4013, 4019, 4021, 4027, 4049, 4051, 4057,
4073, 4079, 4091, 4093, 4099, 4111, 4127, 4129, 4133, 4139,
4153, 4157, 4159, 4177, 4201, 4211, 4217, 4219, 4229, 4231,
4241, 4243, 4253, 4259, 4261, 4271, 4273, 4283, 4289, 4297,
4327, 4337, 4339, 4349, 4357, 4363, 4373, 4391, 4397, 4409,
4421, 4423, 4441, 4447, 4451, 4457, 4463, 4481, 4483, 4493,
4507, 4513, 4517, 4519, 4523, 4547, 4549, 4561, 4567, 4583,
4591, 4597, 4603, 4621, 4637, 4639, 4643, 4649, 4651, 4657,
4663, 4673, 4679, 4691, 4703, 4721, 4723, 4729, 4733, 4751,
4759, 4783, 4787, 4789, 4793, 4799, 4801, 4813, 4817, 4831,
4861, 4871, 4877, 4889, 4903, 4909, 4919, 4931, 4933, 4937,
4943, 4951, 4957, 4967, 4969, 4973, 4987, 4993, 4999, 5003,
5009, 5011, 5021, 5023, 5039, 5051, 5059, 5077, 5081, 5087,
5099, 5101, 5107, 5113, 5119, 5147, 5153, 5167, 5171, 5179,
5189, 5197, 5209, 5227, 5231, 5233, 5237, 5261, 5273, 5279,
5281, 5297, 5303, 5309, 5323, 5333, 5347, 5351, 5381, 5387,
5393, 5399, 5407, 5413, 5417, 5419, 5431, 5437, 5441, 5443,
5449, 5471, 5477, 5479, 5483, 5501, 5503, 5507, 5519, 5521,
5527, 5531, 5557, 5563, 5569, 5573, 5581, 5591, 5623, 5639,
5641, 5647, 5651, 5653, 5657, 5659, 5669, 5683, 5689, 5693,
5701, 5711, 5717, 5737, 5741, 5743, 5749, 5779, 5783, 5791,
5801, 5807, 5813, 5821, 5827, 5839, 5843, 5849, 5851, 5857,
5861, 5867, 5869, 5879, 5881, 5897, 5903, 5923, 5927, 5939,
5953, 5981, 5987, 6007, 6011, 6029, 6037, 6043, 6047, 6053,
6067, 6073, 6079, 6089, 6091, 6101, 6113, 6121, 6131, 6133,
6143, 6151, 6163, 6173, 6197, 6199, 6203, 6211, 6217, 6221,
6229, 6247, 6257, 6263, 6269, 6271, 6277, 6287, 6299, 6301,
6311, 6317, 6323, 6329, 6337, 6343, 6353, 6359, 6361, 6367,
6373, 6379, 6389, 6397, 6421, 6427, 6449, 6451, 6469, 6473,
6481, 6491, 6521, 6529, 6547, 6551, 6553, 6563, 6569, 6571,
6577, 6581, 6599, 6607, 6619, 6637, 6653, 6659, 6661, 6673,
6679, 6689, 6691, 6701, 6703, 6709, 6719, 6733, 6737, 6761,
6763, 6779, 6781, 6791, 6793, 6803, 6823, 6827, 6829, 6833,
6841, 6857, 6863, 6869, 6871, 6883, 6899, 6907, 6911, 6917,
6947, 6949, 6959, 6961, 6967, 6971, 6977, 6983, 6991, 6997,
7001, 7013, 7019, 7027, 7039, 7043, 7057, 7069, 7079, 7103,
7109, 7121, 7127, 7129, 7151, 7159, 7177, 7187, 7193, 7207,
7211, 7213, 7219, 7229, 7237, 7243, 7247, 7253, 7283, 7297,
7307, 7309, 7321, 7331, 7333, 7349, 7351, 7369, 7393, 7411,
7417, 7433, 7451, 7457, 7459, 7477, 7481, 7487, 7489, 7499,
7507, 7517, 7523, 7529, 7537, 7541, 7547, 7549, 7559, 7561,
7573, 7577, 7583, 7589, 7591, 7603, 7607, 7621, 7639, 7643,
7649, 7669, 7673, 7681, 7687, 7691, 7699, 7703, 7717, 7723,
7727, 7741, 7753, 7757, 7759, 7789, 7793, 7817, 7823, 7829,
7841, 7853, 7867, 7873, 7877, 7879, 7883, 7901, 7907, 7919,
7927, 7933, 7937, 7949, 7951, 7963, 7993, 8009, 8011, 8017,
8039, 8053, 8059, 8069, 8081, 8087, 8089, 8093, 8101, 8111,
8117, 8123, 8147, 8161, 8167, 8171, 8179, 8191, 8209, 8219,
8221, 8231, 8233, 8237, 8243, 8263, 8269, 8273, 8287, 8291,
8293, 8297, 8311, 8317, 8329, 8353, 8363, 8369, 8377, 8387,
8389, 8419, 8423, 8429, 8431, 8443, 8447, 8461, 8467, 8501,
8513, 8521, 8527, 8537, 8539, 8543, 8563, 8573, 8581, 8597,
8599, 8609, 8623, 8627, 8629, 8641, 8647, 8663, 8669, 8677,
8681, 8689, 8693, 8699, 8707, 8713, 8719, 8731, 8737, 8741,
8747, 8753, 8761, 8779, 8783, 8803, 8807, 8819, 8821, 8831,
8837, 8839, 8849, 8861, 8863, 8867, 8887, 8893, 8923, 8929,
8933, 8941, 8951, 8963, 8969, 8971, 8999, 9001, 9007, 9011,
9013, 9029, 9041, 9043, 9049, 9059, 9067, 9091, 9103, 9109,
9127, 9133, 9137, 9151, 9157, 9161, 9173, 9181, 9187, 9199,
9203, 9209, 9221, 9227, 9239, 9241, 9257, 9277, 9281, 9283,
9293, 9311, 9319, 9323, 9337, 9341, 9343, 9349, 9371, 9377,
9391, 9397, 9403, 9413, 9419, 9421, 9431, 9433, 9437, 9439,
9461, 9463, 9467, 9473, 9479, 9491, 9497, 9511, 9521, 9533,
9539, 9547, 9551, 9587, 9601, 9613, 9619, 9623, 9629, 9631,
9643, 9649, 9661, 9677, 9679, 9689, 9697, 9719, 9721, 9733,
9739, 9743, 9749, 9767, 9769, 9781, 9787, 9791, 9803, 9811,
9817, 9829, 9833, 9839, 9851, 9857, 9859, 9871, 9883, 9887,
9901, 9907, 9923, 9929, 9931, 9941, 9949, 9967, 9973, 10007
]
function getNthPrime(i: number) {
if (i < primeTable.length) {
return primeTable[i];
}
for (let j = 0; j < i + 1 - primeTable.length; j++) {
makeNextPrime();
}
return primeTable[i];
}
function makeNextPrime() {
const lastPrime = primeTable[primeTable.length - 1];
let candidate = lastPrime + 2;
for (; ;) {
if (!primeTable.some(p => (candidate % p) == 0)) {
break;
}
candidate += 2;
}
primeTable.push(candidate);
}
export function getLargestPrimeFactor(n: number) {
let rest = n;
let i = 0;
let largest = 0;
for (; ;) {
const end = Math.floor(Math.sqrt(rest));
const p = getNthPrime(i);
if (p > end) {
break;
}
while (rest % p === 0) {
rest = Math.floor(rest / p);
largest = p;
}
i++;
}
if (largest < rest) largest = rest;
return largest;
}
export function largestPrimeSolve(n: number): number {
return getLargestPrimeFactor(n)
}
export const testcases:Array<[number,number]> = [
[2, 2],
[3, 3],
[4, 2],
[5, 5],
[6, 3],
[7, 7],
[8, 2],
[9, 3],
[10, 5],
[11, 11],
[12, 3],
[13, 13],
[14, 7],
[15, 5],
[16, 2],
[17, 17],
[18, 3],
[19, 19],
[20, 5],
[21, 7],
[22, 11],
[23, 23],
[24, 3],
[25, 5],
[26, 13],
[27, 3],
[28, 7],
[29, 29],
[30, 5],
[31, 31],
[32, 2],
[33, 11],
[34, 17],
[35, 7],
[36, 3],
[37, 37],
[38, 19],
[39, 13],
[40, 5],
[41, 41],
[42, 7],
[43, 43],
[44, 11],
[45, 5],
[46, 23],
[47, 47],
[48, 3],
[49, 7],
[50, 5],
[51, 17],
[52, 13],
[53, 53],
[54, 3],
[55, 11],
[56, 7],
[57, 19],
[58, 29],
[59, 59],
[60, 5],
[61, 61],
[62, 31],
[63, 7],
[64, 2],
[65, 13],
[66, 11],
[67, 67],
[68, 17],
[69, 23],
[70, 7],
[71, 71],
[72, 3],
[73, 73],
[74, 37],
[75, 5],
[76, 19],
[77, 11],
[78, 13],
[79, 79],
[80, 5],
[81, 3],
[82, 41],
[83, 83],
[84, 7],
[85, 17],
[86, 43],
[87, 29],
[88, 11],
[89, 89],
[90, 5],
[91, 13],
[92, 23],
[93, 31],
[94, 47],
[95, 19],
[96, 3],
[97, 97],
[98, 7],
[99, 11],
]
//if (import.meta.main) {
// for (const [data, ans] of testcases) {
// console.log(data,"\t",getLargestPrimeFactor(data),"\t", ans)
// }
//}

View File

@ -0,0 +1,59 @@
//Minimum Path Sum in a Triangle
export function minimumTrianglePathSumSolve(triangle:number[][]):number{
const minSum = [[triangle[0][0]]];
for (let i = 1; i < triangle.length; i++) {
const arr = triangle[i];
const tmp = [];
for (let j = 0; j < arr.length; j++) {
const cur = triangle[i][j];
if(j - 1 < 0){
tmp.push(minSum[i-1][j]+ cur);
}
else if(j >= triangle[i-1].length){
tmp.push(minSum[i-1][j-1] + cur);
}
else {
tmp.push(Math.min(minSum[i-1][j-1],minSum[i-1][j])+ cur);
}
}
minSum.push(tmp);
}
return Math.min(...minSum[minSum.length - 1]);
}
const case1 = [
[2],
[3,4],
[6,5,7],
[4,1,8,3]
];
const case2 = [
[3],
[8,5],
[3,5,4],
[2,6,3,4],
[9,6,5,5,3],
[6,1,6,5,2,4],
[6,7,7,1,1,8,6],
[7,6,5,9,3,9,2,2],
[6,3,4,6,9,2,8,8,5],
[1,4,6,3,6,5,5,1,9,2],
[1,6,3,6,6,7,2,9,6,6,1],
[1,3,9,8,3,7,1,7,4,5,6,6]
];
interface TestCase{
data: number[][];
ans: number;
}
export const testcases: TestCase[] = [
{
data:case1,
ans:6,
},
{
data:case2,
ans:35
}
];

View File

@ -0,0 +1,161 @@
/**
* You are located in the top-left corner of the following grid:
*
* [[0,0,0,0,0,0,0,0,1,0],
* [0,0,0,1,0,1,0,0,1,0],
* [0,0,0,1,0,0,0,0,0,0],
* [0,0,1,0,0,0,0,0,0,1],
* [0,0,0,0,1,0,0,0,1,0],
* [0,0,1,1,0,1,0,0,0,0],
* [1,1,0,0,0,0,0,0,1,0],
* [0,0,1,0,0,0,0,0,0,0],
* [0,1,1,1,0,0,1,0,0,1],
* [0,1,1,0,0,1,0,0,0,0],
* [1,0,0,1,0,1,0,0,0,0],
* [0,1,0,0,0,0,0,0,0,0]]
*
* You are trying to find the shortest path to the bottom-right
* corner of the grid, but there are obstacles on the grid that
* you cannot move onto. These obstacles are denoted by '1', wh
* ile empty spaces are denoted by 0.
*
* Determine the shortest path from start to finish, if one exi
* sts. The answer should be given as a string of UDLR characte
* rs, indicating the moves along the path
*
* NOTE: If there are multiple equally short paths, any of them
* is accepted as answer. If there is no path, the answer
* should be an empty string.
* NOTE: The data returned for this contract is an 2D array of
* numbers representing the grid.
*
* Examples:
*
* [[0,1,0,0,0],
* [0,0,0,1,0]]
*
* Answer: 'DRRURRD'
*
* [[0,1],
* [1,0]]
*
* Answer: ''
*/
import { SolveFailError } from "./unsolved";
export function shortestPathInGrid(map: number[][]): string[] {
const distMap = makeDistanceMap(map, 0, 0);
if (!isFinite(distMap[map.length - 1][map[map.length - 1].length - 1])) {
return [];
}
const pathes = rewindMap(distMap, map, map[map.length - 1].length - 1, map.length - 1)
return reversePath(pathes);
}
export function shortestPathInGridSolve(map: number[][]): string[] {
return [shortestPathInGrid(map).join("")];
}
function copyMap<T>(map: number[][], fillValue: T): T[][] {
return map.map(x =>
x.map(_ => fillValue)
);
}
function makeDistanceMap(map: number[][], initPosX: number, initPosY: number): number[][] {
const distMap = copyMap(map, Infinity);
const queue: Array<[number, number]> = [];
queue.push([initPosX, initPosY]);
distMap[initPosY][initPosX] = 0;
while (queue.length > 0) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const [x, y] = queue.pop()!;
const currentDist = distMap[y][x];
//visit neighbor
const neighborhood: Array<[number, number]> = [[x + 1, y], [x, y + 1], [x, y - 1], [x - 1, y]]
for (const [nextX, nextY] of neighborhood) {
if (isVisitable(map, nextX, nextY)) {
if (distMap[nextY][nextX] > currentDist + 1) {
//set distance
distMap[nextY][nextX] = currentDist + 1;
//next nodes
queue.push([nextX, nextY]);
}
}
}
}
return distMap;
}
function isVisitable(map: number[][], x: number, y: number): boolean {
return 0 <= y && y < map.length && 0 <= x && x < map[y].length && map[y][x] === 0
}
type PathDir = "R" | "D" | "U" | "L";
function rewindMap(distMap: number[][], map: number[][], initPosX: number, initPosY: number): PathDir[] {
let x = initPosX, y = initPosY;
const ret: PathDir[] = [];
while (getMapDist([x, y]) > 0) {
//walk
const neighborhood: Array<[number, number]> = [[x + 1, y], [x, y + 1], [x, y - 1], [x - 1, y]]
const dirNames: PathDir[] = ["R", "D", "U", "L"];
const candidate = neighborhood.map((x, i) => ({ p: x, index: i }))
.filter(({ p: [cx, cy] }) => isVisitable(map, cx, cy))
if (candidate.length === 0) {
throw new SolveFailError("candidate 0");
}
const next = candidate.reduce((v1, v2) => getMapDist(v1.p) < getMapDist(v2.p) ? v1 : v2)
x = next.p[0];
y = next.p[1];
ret.push(dirNames[next.index]);
}
return ret;
function getMapDist([x, y]: [number, number]): number {
return distMap[y][x];
}
}
const REVERSE_TABLE: { [key in PathDir]: PathDir } = {
"R": "L",
"L": "R",
"D": "U",
"U": "D",
}
/**
* this method mutate array.
* @param path path to reverse
* @returns same reference of input array
*/
function reversePath(path: PathDir[]): PathDir[] {
return path.reverse().map(x => REVERSE_TABLE[x]);
}
//const map = [
// [0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
// [0, 0, 0, 1, 0, 1, 0, 0, 1, 0],
// [0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
// [0, 0, 1, 0, 0, 0, 0, 0, 0, 1],
// [0, 0, 0, 0, 1, 0, 0, 0, 1, 0],
// [0, 0, 1, 1, 0, 1, 0, 0, 0, 0],
// [1, 1, 0, 0, 0, 0, 0, 0, 1, 0],
// [0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
// [0, 1, 1, 1, 0, 0, 1, 0, 0, 1],
// [0, 1, 1, 0, 0, 1, 0, 0, 0, 0],
// [1, 0, 0, 1, 0, 1, 0, 0, 0, 0],
// [0, 1, 0, 0, 0, 0, 0, 0, 0, 0]
//]
//
//import {sprintf} from "https://deno.land/std@0.152.0/fmt/printf.ts";
//function sprintDistMap(distMap:number[][]){
// return (distMap.map(x=>x.map(y=>isFinite(y)? y : "I")
// .map(y=>sprintf("%2s",y.toString())).join(",")).join("\n"));
//}
//const distMap = makeDistanceMap(map,0,0);
//console.log(sprintDistMap(distMap));
//const pathes = rewindMap(distMap,map,map[map.length - 1].length - 1, map.length - 1)
//console.log(reversePath(pathes));

90
src/lib/solve/spiral.ts Normal file
View File

@ -0,0 +1,90 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
enum Direction{
RIGHT = 0,
DOWN = 1,
LEFT = 2,
UP = 3
}
export function spiral(arr: number[][]):number[] {
const w = arr[0].length;
const h = arr.length;
let x = -1, y = 0;
let wdepth = w;
let hdepth = h - 1;
let dir:Direction = Direction.RIGHT;
const ret: number[] = [];
//ret.push(arr[y][x]);
for (;;) {
if(wdepth == 0) break;
//console.log(wdepth);
stepN(wdepth);
dir = turn90(dir);
if(hdepth == 0) break;
//console.log(hdepth);
stepN(hdepth);
dir = turn90(dir);
wdepth--;
hdepth--;
}
return ret;
function turn90(d: Direction){
return (d + 1)% 4;
}
function stepN(n:number){
for (let i = 0; i < n; i++) {
step();
}
}
function step(){
switch(dir){
case Direction.RIGHT:
x++;
break;
case Direction.DOWN:
y++;
break;
case Direction.LEFT:
x--;
break;
case Direction.UP:
y--;
break;
}
ret.push(arr[y][x]);
}
}
export function spiralSolve(arr:number[][]):string[]{
return spiral(arr).map(x=>x.toString());
}
const case1: [number[][],number[]] = [[[1,2,3],
[4,5,6],
[7,8,9]],
[1, 2, 3, 6, 9, 8, 7, 4, 5]
];
const case2: [number[][],number[]] =[
[[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],],
[1, 2, 3, 4, 8,12, 11, 10, 9, 5,6, 7]
];
const case3: [number[][],number[]] = [[
[33,11,20,23,11,47,31,16,41,49],
[37,32,14,34,30,18,15,17,45,36],
[ 9,22,40,35,19,12,23,16,37, 5],
],
[33, 11, 20, 23, 11, 47, 31, 16, 41,49, 36, 5, 37, 16, 23, 12, 19, 35,40, 22, 9, 37, 32, 14, 34, 30, 18,15, 17, 45]
]
export const testcases: Array<[number[][],number[]]> = [
case1,
case2,
case3
];

View File

@ -0,0 +1,58 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
function* localmaxSum(arr: number[], first: number, last: number){
const firstIndex = arr.findIndex(x=> x > 0);
if(firstIndex < 0) return;
let sum = 0;
let blockSum = 0;
for (let index = first; index <= last; index++) {
const element = arr[index];
blockSum += element;
if(blockSum > 0){
sum += blockSum;
blockSum = 0;
}
else if(sum + blockSum < 0){
yield sum;
blockSum = 0;
sum = 0;
}
}
yield sum;
}
export function getMaximumlSumSubArray(arr:number[]): number{
let maxsum = 0;
for(const g of localmaxSum(arr,0,arr.length - 1)){
if(maxsum < g){
maxsum = g;
}
}
if(maxsum === 0){
return Math.max(...arr);
}
return maxsum;
}
export function subarrayMaxSolve(arr:number[]): number{
return getMaximumlSumSubArray(arr);
}
type TestCase = [number[],number];
export const testcases:TestCase[]=[
[[1,5,-9,4],6],
[[9,4,4,0],17],
[[1,-90,5],5],
[[1,2,-50,6],6],
[[1,2,-90,1,4],5],
[[1,2,-90,1,1,1,1],4],
[[1,-90,91],91],
[[-1,-2,-4],-1],
[[-1,3,6],9],
[[5,-50,4,-1,3],6]
]
//if(import.meta.main){
// for (const [arr,s] of testcases) {
// console.log("predict: ",solve(arr)," actual: ", s);
// }
//}

View File

@ -0,0 +1 @@
export class SolveFailError extends Error{}

54
src/list-all-server.ts Normal file
View File

@ -0,0 +1,54 @@
import {NS} from "@ns"
import { parse } from "./lib/flag";
import {getHackability,getLockSymbol} from "./lib/hack";
import { sprintf } from "./lib/printf";
let detail = false;
/** @param {NS} ns */
// eslint-disable-next-line require-await
export async function main(ns: NS): Promise<void> {
const flag = parse(ns.args.map(String));
detail = flag.d || flag.detail || false;
const a = findServer(ns, 'home', 'home', 1);
ns.tprint("\n"+a.join("\n"));
}
/** @param {NS} ns
* @param {string} startServer
* @param {string} targetServer
* @param {number} depth
*/
function findServer(ns: NS, startServer: string, targetServer: string, depth: number): string[] {
const servers = ns.scan(targetServer)
.filter((server) => server !== startServer);
return servers.map((server, i, arr) => {
const lock = getHackability(ns, server);
const lock_symbol = getLockSymbol(lock);
const info = ns.getServer(server);
const backdoorSymbol = info.backdoorInstalled ? '✅': '❌';
let ret: string[] = [];
const startSymbol = (i + 1 == arr.length) ? "└" : "├";
const extendSymbol = (i + 1 == arr.length) ? " " : "│";
//printf(ns, `😹${'>'.repeat(depth)} %s %s %s`,lock_symbol,server,info.backdoorInstalled ? '✅': '❌')
const fmsg = (`${startSymbol}${lock_symbol} ${backdoorSymbol} ${server}`);
ret.push(fmsg);
if(detail){
const moneyAvailable = ns.nFormat(info.moneyAvailable,"$0.000a");
const moneyMax = ns.nFormat(info.moneyMax,"$0.000a");
ret.push(sprintf("%s └%s🛡 %6.2f/%6.2f(%3d),💸 %10s/%10s 💾%d/%d",extendSymbol,
"-".repeat(20-depth),
info.hackDifficulty,info.minDifficulty,info.requiredHackingSkill,
moneyAvailable,moneyMax,
info.maxRam, info.ramUsed));
}
if (lock !== "impossible") {
const s = findServer(ns, targetServer, server, depth + 1).map(x=>`${extendSymbol}${x}`);
ret = ret.concat(s)
}
return ret;
}).flat();
}

View File

@ -0,0 +1,13 @@
import {NS} from "@ns"
import {parse} from "lib/flag"
/** @param {NS} ns */
// eslint-disable-next-line require-await
export async function main(ns: NS):Promise<void> {
const flag = parse(ns.args.map(x=>String(x)));
if(flag.help || flag.h){
ns.tprint(`* list all purchased server`);
ns.exit();
}
ns.tprint(ns.getPurchasedServers().join("\n"));
}

63
src/list-stock.ts Normal file
View File

@ -0,0 +1,63 @@
import { NS } from '@ns'
import { parse } from './lib/flag';
import { sprintf } from './lib/printf';
interface StockStatsticsInfo{
stockName: string;
price: number;
forecast: number;
vaolatility: number;
expectation: number;
variation: number;
std:number;
askPrice:number;
bidPrice:number;
maxShare:number;
}
export function getStockStatsticsList(ns:NS): StockStatsticsInfo[]{
return ns.stock.getSymbols().map(x=>getStockStatstics(ns,x));
}
export function getStockStatstics(ns:NS, stockName:string):StockStatsticsInfo{
const forecast = ns.stock.getForecast(stockName);
const val = ns.stock.getVolatility(stockName);
const price = ns.stock.getPrice(stockName);
const askPrice = ns.stock.getAskPrice(stockName);
const bidPrice = ns.stock.getBidPrice(stockName);
const maxShare = ns.stock.getMaxShares(stockName);
const priceVar = val * price;
const expectation = (2*forecast - 1) * priceVar;
const variation = 4*priceVar*(1-forecast)*forecast;
return {
stockName,
price,
forecast,
vaolatility: val,
expectation: expectation,
variation: variation,
std: Math.sqrt(variation),
askPrice,
bidPrice,
maxShare,
};
}
// eslint-disable-next-line require-await
export async function main(ns: NS) : Promise<void> {
const flag = parse(ns.args.map(x=>x.toString()));
if(flag.h || flag.help){
const msg = ['run cmd']
msg.forEach(x=>ns.tprint(x));
return;
}
const list = getStockStatsticsList(ns);
list.sort((a,b)=>b.expectation - a.expectation );
list.forEach(info=>{
ns.tprint(sprintf("%6s %8s %2.1f%% %8s %8s",info.stockName,
ns.nFormat(info.price,"$0.00a"),
info.forecast*100,
ns.nFormat(info.expectation,"$0.00a"),
ns.nFormat(info.std,"$0.00a")))
})
}

87
src/ls-contract.ts Normal file
View File

@ -0,0 +1,87 @@
import { NS, AutocompleteData } from '@ns'
import {selectAllContract, getContractList, RemoteFilePath} from "lib/contract";
import { sprintf } from './lib/printf';
import { parse } from './lib/flag';
import Fuse from "lib/fuse.esm";
function searchFilename(ns: NS, filename: string, hostname?: string): RemoteFilePath | null{
let p: RemoteFilePath[];
if(hostname){
p = getContractList(ns,hostname);
}
else{
p = selectAllContract(ns);
}
const fuse = new Fuse(p, {includeScore:true, keys:["filename"]});
const candiates = fuse.search(filename);
if(candiates.length === 0){
return null;
}
const candiate = candiates[0];
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
if(candiate!.score! > 0.8){
return null;
}
return candiate.item;
}
// eslint-disable-next-line require-await
export async function main(ns : NS) : Promise<void> {
const capi = ns.codingcontract;
if(ns.args.length === 0){
const ctList = selectAllContract(ns);
ctList.map(ct=>{
const type = capi.getContractType(ct.filename,ct.hostname);
return {
...ct,
type
};
}).sort((a,b)=>(a.type>b.type) ? 1 : -1)
.forEach(ct=>{
const msg = sprintf("%17s %40s %s",ct.hostname,ct.filename,ct.type)
ns.tprint(msg);
})
return;
}
const flag = parse(ns.args.map(x=>x.toString()));
if(flag.h || flag.help){
ns.tprint("HELP : ")
ns.tprint("run cmd [filename] [--host|-t]");
return;
}
let hostFlag : string|undefined;
if(flag.host || flag.t){
hostFlag = flag.host.toString() || flag.t.toString();
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
if(!ns.serverExists(hostFlag!)){
ns.tprint("unexist hostname");
return;
}
}
const inputFilename = flag._.toString();
const p = searchFilename(ns,inputFilename, hostFlag);
if(p === null){
ns.tprint(`could not file ${inputFilename}`);
return;
}
const {filename, hostname: target} = p;
ns.tprint(`detail of ${target}:${filename}`);
const msg = [
`${filename}(${capi.getNumTriesRemaining(filename,target)}/10)`,
`${capi.getContractType(filename,target)}`,
`${capi.getDescription(filename,target).replaceAll("&nbsp;"," ")}`,
`${JSON.stringify(capi.getData(filename,target))}`
].join("\n");
ns.tprint("\n",msg);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function autocomplete(data : AutocompleteData, args : string[]) : string[] {
if(args.length == 1){
return [...data.servers]
}
return [];
}

58
src/lsnode.ts Normal file
View File

@ -0,0 +1,58 @@
import { selectRootedServerList, ServerInfo } from "lib/servers";
import {
calculateWeakenTime,
calculatePercentMoneyHacked,
calculateHackingExpGain,
calculateServerGrowth
} from "lib/formula";
import { NS } from '@ns'
import { sprintf } from "./lib/printf";
import { parse } from "./lib/flag";
// eslint-disable-next-line require-await
export async function main(ns: NS): Promise<void> {
const flag = parse(ns.args.map(x=>x.toString()))
const list = selectRootedServerList(ns);
const player = ns.getPlayer();
const m = list.map(x => {
const optimalState: ServerInfo = {...x,
hackDifficulty: x.minDifficulty
} as ServerInfo;
ns.print(optimalState.minDifficulty," ", optimalState.hackDifficulty ,"");
const weakenTime = calculateWeakenTime(optimalState, player);
const expGain = calculateHackingExpGain(optimalState,player);
const earnMoney = calculatePercentMoneyHacked(optimalState, player);
//const growPercent = calculateServerGrowth()
return {
hostname: x.hostname,
info: x,
weakenTime,
earnMoney,
ce: earnMoney* x.moneyMax/weakenTime,
expCe: expGain / weakenTime,
expGain,
}
});
if(flag.exp){
m.sort((a,b)=>(b.expCe-a.expCe));
m.filter(x=>x.expCe > 0).forEach(x=>{
const msg = sprintf("%20s %8s %6.1fs %8s",x.hostname,
ns.nFormat(x.expGain,"0.00a"),x.weakenTime,
ns.nFormat(x.expCe,"0.00a"));
ns.tprint(msg);
})
}
else {
m.sort((a,b)=>(b.ce-a.ce));
m.filter(x=>x.ce > 0).forEach(x=>{
const msg = sprintf("%20s %8s %6.1fs %10s",x.hostname,
ns.nFormat(x.earnMoney * x.info.moneyMax,"$0.00a"),x.weakenTime,
ns.nFormat(x.ce,"$0.000a"));
ns.tprint(msg);
})
}
}

73
src/purchase-server.ts Normal file
View File

@ -0,0 +1,73 @@
import { NS } from "@ns"
import {parse} from "./lib/flag"
import {range} from "./util/range";
/** @param {NS} ns */
export async function main(ns: NS): Promise<void> {
const flag = parse(ns.args.map(x=>String(x)));
if(flag.h || flag.help){
ns.tprint("script : purchase server")
ns.tprint("");
ns.exit()
}
if(Boolean(flag.i) || Boolean(flag.interactive)){
const ramLimit = ns.getPurchasedServerMaxRam()
const choices = [...range(3,21)].map(x=>Math.pow(2,x)).filter(x=>x <= ramLimit).map((x)=>{
const cost = ns.getPurchasedServerCost(x);
return `${x}GB (${ns.nFormat(cost,"$0.000a")})`
});
const choice = await ns.prompt("which server do you purchase?",{
type:"select",
choices
}) as string;
if(choice === ""){
ns.tprint("canceled");
ns.exit();
return;
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const gb = parseInt(/^(\d+)GB/.exec(choice)![1]);
ns.tprint("you select ",gb,"GB");
const hostname = await ns.prompt("name your server",{type:"text"}) as string;
if(hostname === ""){
ns.tprint("canceled");
ns.exit();
return;
}
const p = await ns.prompt(
`do you want to purchase server ${gb}GB(${
ns.nFormat(ns.getPurchasedServerCost(gb),"$0.000a")})?`,
{ type: "boolean" });
if (p) {
const l = ns.purchaseServer(hostname, gb);
ns.tprint(l, " purchased");
}
ns.exit()
return;
}
if ((flag.n || flag.name) && (flag.s || flag.size)) {
ns.tprint("argument needed")
ns.tprint("run cmd -n [hostname] -s [ram(GB)]")
ns.tprint("run cmd --name [hostname] --size [ram(GB)]")
ns.exit()
return;
}
const hostname = flag.n ?? flag.name
const ram = parseInt(flag.s ?? flag.size)
if(isNaN(ram)){
ns.tprint("size must be integer!");
ns.exit();
return;
}
const f = ns.getPurchasedServerCost(ram)
const ff = ns.nFormat(f, "($ 0.00 a)")
const p = await ns.prompt("required : " + ff + "\ndo you want to purchase server " + ram + "GB?",
{ type: "boolean" })
if (p) {
const l = ns.purchaseServer(hostname, ram)
ns.tprint(l, " purchased")
}
}

20
src/rainbow.ts Normal file
View File

@ -0,0 +1,20 @@
import { NS } from '@ns'
import { parse } from './lib/flag';
import { sprintf } from './lib/printf';
function guess(ns:NS,text:string):void{
// undocumented function
// @ts-ignore TS2339
return ns.rainbow(text);
}
// eslint-disable-next-line require-await
export async function main(ns: NS) : Promise<void> {
const flag = parse(ns.args.map(x=>x.toString()));
if(flag.h || flag.help){
const msg = ['run cmd']
msg.forEach(x=>ns.tprint(x));
return;
}
guess(ns,"noodles")
}

11
src/repl.ts Normal file
View File

@ -0,0 +1,11 @@
import { NS } from '@ns'
export async function main(ns: NS) : Promise<void> {
const cmd = ns.args.join(" ");
// @ts-ignore: disable-next-line
const fn = new Function(["ns"],cmd);
const v = await fn(ns);
if(v !== undefined){
ns.tprint(v)
}
}

30
src/runtest.ts Normal file
View File

@ -0,0 +1,30 @@
import { NS } from '@ns'
import { parse } from './lib/flag';
import { red, rgb24, blue, bgBrightMagenta } from './lib/colors';
// eslint-disable-next-line require-await
export async function main(ns: NS) : Promise<void> {
const flag = parse(ns.args.map(x=>x.toString()));
if(flag.h || flag.help){
const msg = ['run cmd']
msg.forEach(x=>ns.tprint(x));
return;
}
ns.tprint(red("hello world red "));
ns.tprint(blue("hello world blue"));
ns.tprint(bgBrightMagenta("hello world bgBrightMagenta"));
ns.tprint(rgb24("hello world #00ff00", 0x00ff00));
ns.tprint(rgb24("hello world #f0f000", 0xf0f000));
ns.tprint(rgb24("hello world #0000ff", 0x0000ff));
ns.tprint(rgb24("hello world #0000ff", {r:0,g:0,b:255}));
//const d = "document"
//const doc = eval(d) as Document;
//const div = doc.querySelector(".css-1hamw82") as HTMLDivElement;
//if(!div){
// ns.tprint("div not found");
// return;
//}
//const text = div.innerText;
//ns.tprint(text);
}

95
src/server-status.ts Normal file
View File

@ -0,0 +1,95 @@
import {NS, AutocompleteData, Server} from "@ns";
import {hackablePorts, getHackability, getLockSymbol} from "./lib/hack";
import {parse} from "./lib/flag";
type ReportOption = {
detail?: boolean;
}
/*
* Utility functions that report serverStatus
* and Hackability
* @param {NS} ns
*/
// eslint-disable-next-line require-await
export async function main(ns: NS): Promise<void> {
const flag = parse(ns.args.map(x=>String(x)));
if (flag._.length == 0) {
ns.tprint("argumented required");
return;
}
const hostname = String(flag._[0]);
const detail = Boolean(flag.d) || Boolean(flag.detail);
const realtime = Boolean(flag.realtime);
if(realtime){
ns.tail();
for(;;){
const server = ns.getServer(hostname);
ns.clearLog();
const msg = makeDetailServerReport(ns,server);
ns.print(msg);
await ns.sleep(1000);
}
}
else{
serverReport(ns, hostname,{
detail: detail,
});
}
}
function makeDetailServerReport(ns:NS,server:Server): string{
return [`hostname : ${server.hostname}(${server.ip})`,
`🛡️${ns.nFormat(server.hackDifficulty,"0[.]000")}/${server.minDifficulty}(${server.baseDifficulty})`,
`💸${ns.nFormat(server.moneyAvailable,"$0.000a")}/${ns.nFormat(server.moneyMax,"$0.000a")}`,
`💾${server.ramUsed}GB/${server.maxRam}GB`,
`backdoorInstalled \t: ${server.backdoorInstalled ? `🚪` : ``}`,
`cpu \t\t\t: ${server.cpuCores}`,
`growth \t\t\t: ${server.serverGrowth}`,
`organization name\t: ${server.organizationName}`,
`purchased \t\t: ${server.purchasedByPlayer}`,
`required hacking skill\t: ${server.requiredHackingSkill}`,
`ports \t\t\t: ${server.openPortCount}/${server.numOpenPortsRequired}`
].join("\n");
}
/**
* @param {NS} ns
* @param {string} hostname
*/
export function serverReport(ns: NS, hostname: string, options?:ReportOption):void {
options ??= {};
const serverLock = getHackability(ns, hostname);
ns.tprint(`${getLockSymbol(serverLock)} ${hostname}`);
if(options.detail){
const server = ns.getServer(hostname);
ns.tprint("\n"+makeDetailServerReport(ns, server));
}
else{
if (serverLock == "rooted") {
ns.tprint(`🛡️${Math.round(ns.getServerSecurityLevel(hostname))}/${ns.getServerMinSecurityLevel(hostname)}`);
ns.tprint(`💸${ns.nFormat(ns.getServerMoneyAvailable(hostname), "$0.000a")}/${ns.nFormat(ns.getServerMaxMoney(hostname), "$0.000a")}`);
} else {
ns.tprint(`Hack Level: ${ns.getServerRequiredHackingLevel(hostname)}`);
ns.tprint(`Ports: ${ns.getServerNumPortsRequired(hostname)}`);
}
}
}
/**
* @param {NS} ns
*/
export function serverHackStatus(ns: NS, server: string): string {
if (ns.hasRootAccess(server)) {
return "🔓";
}
if (ns.getServerRequiredHackingLevel(server) > ns.getHackingLevel() ||
ns.getServerNumPortsRequired(server) > hackablePorts(ns)) {
return "🔒";
}
return "🔐";
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function autocomplete(data : AutocompleteData, args : string[]) : string[] {
return [...data.servers]
}

8
src/share-server.ts Normal file
View File

@ -0,0 +1,8 @@
import {NS} from "@ns"
// eslint-disable-next-line require-await
export async function main(ns:NS):Promise<void>{
while(true){
await ns.share()
}
}

View File

@ -0,0 +1,35 @@
import { NS } from '@ns'
// eslint-disable-next-line require-await
export async function main(ns : NS) : Promise<void> {
const api = ns.stock;
if(!api.hasWSEAccount() || !api.has4SDataTIXAPI()){
ns.tprint("api need")
ns.tprint("purchase stock API!");
return;
}
ns.print("start stock-auto-sell-daemon");
ns.disableLog("ALL")
ns.tail();
for(;;){
for(const stock of api.getSymbols()){
const p = api.getForecast(stock);
if(p < 0.5){
const share = api.getPosition(stock)[0]
if(share > 0){
ns.print(`forecast: ${p}, sell ${stock} amount of ${share}`);
const v = api.sellStock(stock, share);
if(v == 0){
ns.print("failed to sell stock!");
ns.toast("Failed To Sell Stock","error",6000);
}
else{
ns.print(`avg sold price ${v}`);
ns.toast(`Sell ${stock} amount of ${ns.nFormat(share,"0.000a")}`,"info",6000);
}
}
}
}
await ns.sleep(6000);
}
}

69
src/stock-daemon.ts Normal file
View File

@ -0,0 +1,69 @@
import { NS } from '@ns'
// eslint-disable-next-line require-await
export async function main(ns : NS) : Promise<void> {
const stock = ns.stock;
if(!stock.hasWSEAccount() || !stock.has4SDataTIXAPI()){
ns.tprint("api need")
ns.tprint("purchase stock API!");
return;
}
if(ns.args.length == 0){
ns.tprint("argument need")
return;
}
ns.print("start stock-auto-sell-daemon");
ns.disableLog("ALL")
ns.tail();
const account = ns.args[0];
const tradableStocks = stock.getSymbols();
const p = tradableStocks.map(st=>{
const forecast = stock.getForecast(st);
const val = stock.getVolatility(st);
const price = stock.getPrice(st);
const priceVar = val * price;
const expectation = (2*forecast - 1) * priceVar;
const variation = 4*priceVar*(1-forecast)*forecast;
return {
stockName: st,
expectation: expectation,
variation: variation
};
});
}
//// eslint-disable-next-line require-await
//export async function main(ns : NS) : Promise<void> {
// const api = ns.stock;
// if(!api.hasWSEAccount() || !api.has4SDataTIXAPI()){
// ns.tprint("api need")
// ns.tprint("purchase stock API!");
// return;
// }
// ns.print("start stock-auto-sell-daemon");
// ns.disableLog("ALL")
// ns.tail();
// for(;;){
// for(const stock of api.getSymbols()){
// const p = api.getForecast(stock);
// if(p < 0.5){
// const share = api.getPosition(stock)[0]
// if(share > 0){
// ns.print(`forecast: ${p}, sell ${stock} amount of ${share}`);
// const v = api.sellStock(stock, share);
// if(v == 0){
// ns.print("failed to sell stock!");
// ns.toast("Failed To Sell Stock","error",6000);
// }
// else{
// ns.print(`avg sold price ${v}`);
// ns.toast(`Sell ${stock} amount of ${ns.nFormat(share,"0.000a")}`,"info",6000);
// }
// }
// }
// }
// await ns.sleep(6000);
// }
//}

7
src/unpack-script.ts Normal file
View File

@ -0,0 +1,7 @@
import { NS } from '@ns'
import { installBatchFilePack } from './lib/batchbase';
// eslint-disable-next-line require-await
export async function main(ns : NS) : Promise<void> {
await installBatchFilePack(ns);
}

18
src/util/assert.ts Normal file
View File

@ -0,0 +1,18 @@
export class AssertionError extends Error{
constructor(msg:string){
super(msg);
this.name = "AssertionError";
}
}
export function assert(expr: unknown, msg=""): asserts expr{
if(!expr){
throw new AssertionError(msg)
}
}
export class NotImplementError extends Error{}
export function notImplemented(): never {
throw new NotImplementError();
}

8
src/util/range.ts Normal file
View File

@ -0,0 +1,8 @@
export function* range(arg1: number, arg2?: number): Generator<number>{
const begin = arg2 ? arg1 : 0;
const end = arg2 ?? arg1;
for(let i = begin; i<end; i++){
yield i;
}
}

View File

@ -1,4 +1,4 @@
import { NS, ProcessInfo } from '../NetscriptDefinitions'
import { NS, ProcessInfo } from '@ns'
export async function main(ns: NS): Promise<void> {
const hashes: any = {}
@ -6,27 +6,27 @@ export async function main(ns: NS): Promise<void> {
const files = ns.ls('home', '.js')
for (const file of files) {
const contents = ns.read(file)
hashes[file] = getHash(contents)
hashes[file] = getHash(contents as string)
}
// eslint-disable-next-line no-constant-condition
while (true) {
const files = ns.ls('home', '.js')
for (const file of files) {
const contents = ns.read(file)
const hash = getHash(contents)
const hash = getHash(contents as string)
if (hash != hashes[file]) {
ns.tprint(`INFO: Detected change in ${file}`)
ns.tprintf(`INFO: Detected change in ${file}`)
const processes = ns.ps().filter((p: ProcessInfo) => {
return p.filename == file
})
for (const process of processes) {
ns.tprint(`INFO: Restarting ${process.filename} ${process.args} -t ${process.threads}`)
ns.tprintf(`INFO: Restarting ${process.filename} ${process.args} -t ${process.threads}`)
if (process.filename != ns.getScriptName()) {
ns.kill(process.pid, ns.getHostname())
ns.kill(process.pid)
ns.run(process.filename, process.threads, ...process.args)
} else {
ns.spawn(process.filename, process.threads, ...process.args)

View File

@ -3,7 +3,6 @@
"NetscriptDefinitions.d.ts",
"src/**/*",
],
"compilerOptions": {
"module": "esnext",
"target": "esnext",
@ -11,15 +10,21 @@
"noImplicitReturns": true,
"noImplicitThis": true,
"esModuleInterop": true,
"sourceMap": false,
"inlineSourceMap": true,
"strict": true,
"rootDir": "src/",
"outDir": "dist/",
"baseUrl": "src/",
"paths": {
"/*.js": [ "*" ],
"*.js": [ "*" ]
"/*.js": [
"*"
],
"*.js": [
"*"
],
"@ns": [
"../NetscriptDefinitions.d.ts"
]
}
}
}