write()/read() now work for script files. You can now use angled brackets in tprint() (and create DOM elements). Added CodingContract implementation

This commit is contained in:
danielyxie 2018-09-14 16:03:31 -05:00
parent b66c3c6fc4
commit 420fd0c5ad
12 changed files with 912 additions and 74 deletions

203
src/CodingContracts.ts Normal file

@ -0,0 +1,203 @@
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver";
import { createElement } from "../utils/uiHelpers/createElement";
import { createPopup } from "../utils/uiHelpers/createPopup";
import { removeElementById } from "../utils/uiHelpers/removeElementById";
import { codingContractTypesMetadata, DescriptionFunc, GeneratorFunc, SolverFunc } from "./data/codingcontracttypes";
import { IMap } from "./types";
/* tslint:disable:no-magic-numbers completed-docs max-classes-per-file no-console */
/* Represents different types of problems that a Coding Contract can have */
export class ContractType {
/**
* Function that generates a description of the problem
*/
desc: DescriptionFunc;
/**
* Number that generally represents the problem's difficulty. Bigger numbers = harder
*/
difficulty: number;
/**
* A function that randomly generates a valid 'data' for the problem
*/
generate: GeneratorFunc;
/**
* Name of the type of problem
*/
name: string;
/**
* The maximum number of tries the player gets on this kind of problem before it self-destructs
*/
numTries: number;
/**
* Stores a function that checks if the provided answer is correct
*/
solver: SolverFunc;
constructor(name: string,
desc: DescriptionFunc,
gen: GeneratorFunc,
solver: SolverFunc,
diff: number,
numTries: number) {
this.name = name;
this.desc = desc;
this.generate = gen;
this.solver = solver;
this.difficulty = diff;
this.numTries = numTries;
}
}
/* Contract Types */
// tslint:disable-next-line
export const ContractTypes: IMap<ContractType> = {};
for (const md of codingContractTypesMetadata) {
ContractTypes[md.name] = new ContractType(md.name, md.desc, md.gen, md.solver, md.difficulty, md.numTries);
}
console.info(`${Object.keys(ContractTypes).length} Coding Contract Types loaded`);
/**
* Enum representing the different types of rewards a Coding Contract can give
*/
export enum CodingContractRewardType {
FactionReputation,
FactionReputationAll,
CompanyReputation,
Money,
}
/**
* Enum representing the result when trying to solve the Contract
*/
export enum CodingContractResult {
Success,
Failure,
Cancelled,
}
/**
* A class that represents the type of reward a contract gives
*/
export interface ICodingContractReward {
/* Name of Company/Faction name for reward, if applicable */
name?: string;
type: CodingContractRewardType;
}
/**
* A Coding Contract is a file that poses a programming-related problem to the Player.
* The player receives a reward if the problem is solved correctly
*/
export class CodingContract {
/**
* Initiatizes a CodingContract from a JSON save state.
*/
static fromJSON(value: any): CodingContract {
return Generic_fromJSON(CodingContract, value.data);
}
/* Relevant data for the contract's problem */
data: any;
/* Contract's filename */
fn: string;
/* Describes the reward given if this Contract is solved. The reward is actually
processed outside of this file */
reward: ICodingContractReward | null;
/* Number of times the Contract has been attempted */
tries: number = 0;
/* String representing the contract's type. Must match type in ContractTypes */
type: string;
constructor(fn: string = "",
type: string = "Find Largest Prime Factor",
reward: ICodingContractReward | null = null) {
this.fn = fn;
if (!this.fn.endsWith(".cct")) {
this.fn += ".cct";
}
// tslint:disable-next-line
if (ContractTypes[type] == null) {
throw new Error(`Error: invalid contract type: ${type} please contact developer`);
}
this.type = type;
this.data = ContractTypes[type].generate();
this.reward = reward;
}
getDifficulty(): number {
return ContractTypes[this.type].difficulty;
}
getMaxNumTries(): number {
return ContractTypes[this.type].numTries;
}
isSolution(solution: string): boolean {
return ContractTypes[this.type].solver(this.data, solution);
}
/**
* Creates a popup to prompt the player to solve the problem
*/
async prompt(): Promise<CodingContractResult> {
// tslint:disable-next-line
return new Promise<CodingContractResult>((resolve: Function, reject: Function) => {
const contractType: ContractType = ContractTypes[this.type];
const popupId: string = `coding-contract-prompt-popup-${this.fn}`;
const txt: HTMLElement = createElement("p", {
innerText: ["You are attempting to solve a Coding Contract. Note that",
"you only have one chance. Providing the wrong solution",
"will cause the contract to self-destruct.\n\n",
`${contractType.desc(this.data)}`].join(" "),
});
const answerInput: HTMLInputElement = createElement("input", {
placeholder: "Enter Solution here",
}) as HTMLInputElement;
const solveBtn: HTMLElement = createElement("a", {
class: "a-link-button",
clickListener: () => {
const answer: string = answerInput.value;
if (this.isSolution(answer)) {
resolve(CodingContractResult.Success);
} else {
resolve(CodingContractResult.Failure);
}
removeElementById(popupId);
},
innerText: "Solve",
});
const cancelBtn: HTMLElement = createElement("a", {
class: "a-link-button",
clickListener: () => {
resolve(CodingContractResult.Cancelled);
removeElementById(popupId);
},
innerText: "Cancel",
});
const lineBreak: HTMLElement = createElement("br");
createPopup(popupId, [txt, lineBreak, lineBreak, answerInput, solveBtn, cancelBtn]);
});
}
/**
* Serialize the current file to a JSON save state.
*/
toJSON(): any {
return Generic_toJSON("CodingContract", this);
}
}
Reviver.constructors.CodingContract = CodingContract;

@ -85,6 +85,8 @@ let CONSTANTS = {
ScriptGetScriptRamCost: 0.1, ScriptGetScriptRamCost: 0.1,
ScriptGetHackTimeRamCost: 0.05, ScriptGetHackTimeRamCost: 0.05,
ScriptGetFavorToDonate: 0.10, ScriptGetFavorToDonate: 0.10,
ScriptGetContractDataRamCost: 25,
ScriptAttemptContractRamCost: 25,
ScriptSingularityFn1RamCost: 1, ScriptSingularityFn1RamCost: 1,
ScriptSingularityFn2RamCost: 2, ScriptSingularityFn2RamCost: 2,
@ -191,12 +193,13 @@ let CONSTANTS = {
"-Nodes slowly regenerate health over time.", "-Nodes slowly regenerate health over time.",
//Gang constants /* Gang constant */
GangRespectToReputationRatio: 2, //Respect is divided by this to get rep gain GangRespectToReputationRatio: 2, //Respect is divided by this to get rep gain
MaximumGangMembers: 20, MaximumGangMembers: 20,
GangRecruitCostMultiplier: 2, GangRecruitCostMultiplier: 2,
GangTerritoryUpdateTimer: 150, GangTerritoryUpdateTimer: 150,
/* Time Constants */
MillisecondsPer20Hours: 72000000, MillisecondsPer20Hours: 72000000,
GameCyclesPer20Hours: 72000000 / 200, GameCyclesPer20Hours: 72000000 / 200,
@ -224,6 +227,7 @@ let CONSTANTS = {
MillisecondsPerFiveMinutes: 300000, MillisecondsPerFiveMinutes: 300000,
GameCyclesPerFiveMinutes: 300000 / 200, GameCyclesPerFiveMinutes: 300000 / 200,
/* Player Work / Action related Constants */
FactionWorkHacking: "Faction Hacking Work", FactionWorkHacking: "Faction Hacking Work",
FactionWorkField: "Faction Field Work", FactionWorkField: "Faction Field Work",
FactionWorkSecurity: "Faction Security Work", FactionWorkSecurity: "Faction Security Work",
@ -267,6 +271,11 @@ let CONSTANTS = {
CrimeAssassination: "assassinate a high-profile target", CrimeAssassination: "assassinate a high-profile target",
CrimeHeist: "pull off the ultimate heist", CrimeHeist: "pull off the ultimate heist",
/* Coding Contract Constants */
CodingContractBaseFactionRepGain: 2500,
CodingContractBaseCompanyRepGain: 4000,
CodingContractBaseMoneyGain: 10e6,
/* Tutorial related things */ /* Tutorial related things */
TutorialNetworkingText: "Servers are a central part of the game. You start with a single personal server (your home computer) " + TutorialNetworkingText: "Servers are a central part of the game. You start with a single personal server (your home computer) " +
"and you can purchase additional servers as you progress through the game. Connecting to other servers " + "and you can purchase additional servers as you progress through the game. Connecting to other servers " +
@ -497,7 +506,13 @@ let CONSTANTS = {
"World Stock Exchange account and TIX API Access<br>", "World Stock Exchange account and TIX API Access<br>",
LatestUpdate: LatestUpdate:
`v0.40.3<br> `
v0.40.4<br>
* (TODO NEEDS DOCUMENTATION) The write() and read() Netscript functions now work on scripts<br>
* It is now possible to use freely use angled bracket (<, >) and create DOM elements using tprint()<br>
* Added Coding Contracts (not yet generated in game, but the data/implementation exists)<br>
v0.40.3<br>
-----------------------------------------------<br> -----------------------------------------------<br>
* Bladeburner Changes:<br> * Bladeburner Changes:<br>
*** Increased the effect that agi and dexterity have on action time<br> *** Increased the effect that agi and dexterity have on action time<br>

@ -475,13 +475,6 @@ function NetscriptFunctions(workerScript) {
throw makeRuntimeRejectMsg(workerScript, "tprint() call has incorrect number of arguments. Takes 1 argument"); throw makeRuntimeRejectMsg(workerScript, "tprint() call has incorrect number of arguments. Takes 1 argument");
} }
var x = args.toString(); var x = args.toString();
if (isHTML(x)) {
Player.takeDamage(1);
dialogBoxCreate("You suddenly feel a sharp shooting pain through your body as an angry voice in your head exclaims: <br><br>" +
"DON'T USE TPRINT() TO OUTPUT HTML ELEMENTS TO YOUR TERMINAL!!!!<br><br>" +
"(You lost 1 HP)");
return;
}
post(workerScript.scriptRef.filename + ": " + args.toString()); post(workerScript.scriptRef.filename + ": " + args.toString());
}, },
clearLog : function() { clearLog : function() {
@ -1839,13 +1832,26 @@ function NetscriptFunctions(workerScript) {
throw makeRuntimeRejectMsg(workerScript, "Could not find port: " + port + ". This is a bug contact the game developer"); throw makeRuntimeRejectMsg(workerScript, "Could not find port: " + port + ". This is a bug contact the game developer");
} }
return port.write(data); return port.write(data);
} else if (isString(port)) { //Write to text file } else if (isString(port)) { //Write to script or text file
var fn = port; var fn = port;
var server = getServer(workerScript.serverIp); var server = workerScript.getServer();
if (server == null) { if (server == null) {
throw makeRuntimeRejectMsg(workerScript, "Error getting Server for this script in write(). This is a bug please contact game dev"); throw makeRuntimeRejectMsg(workerScript, "Error getting Server for this script in write(). This is a bug please contact game dev");
} }
var txtFile = getTextFile(fn, server); if (isScriptFilename(fn)) {
//Write to script
let script = workerScript.getScriptOnServer(fn);
if (script == null) {
//Create a new script
script = new Script(fn, data, server.ip);
server.scripts.push(script);
return true;
}
mode === "w" ? script.code = data : script.code += data;
script.updateRamUsage();
} else {
//Write to text file
let txtFile = getTextFile(fn, server);
if (txtFile == null) { if (txtFile == null) {
txtFile = createTextFile(fn, data, server); txtFile = createTextFile(fn, data, server);
return true; return true;
@ -1855,6 +1861,7 @@ function NetscriptFunctions(workerScript) {
} else { } else {
txtFile.append(data); txtFile.append(data);
} }
}
return true; return true;
} else { } else {
throw makeRuntimeRejectMsg(workerScript, "Invalid argument passed in for write: " + port); throw makeRuntimeRejectMsg(workerScript, "Invalid argument passed in for write: " + port);
@ -1895,18 +1902,28 @@ function NetscriptFunctions(workerScript) {
throw makeRuntimeRejectMsg(workerScript, "ERROR: Could not find port: " + port + ". This is a bug contact the game developer"); throw makeRuntimeRejectMsg(workerScript, "ERROR: Could not find port: " + port + ". This is a bug contact the game developer");
} }
return port.read(); return port.read();
} else if (isString(port)) { //Read from text file } else if (isString(port)) { //Read from script or text file
var fn = port; let fn = port;
var server = getServer(workerScript.serverIp); let server = getServer(workerScript.serverIp);
if (server == null) { if (server == null) {
throw makeRuntimeRejectMsg(workerScript, "Error getting Server for this script in read(). This is a bug please contact game dev"); throw makeRuntimeRejectMsg(workerScript, "Error getting Server for this script in read(). This is a bug please contact game dev");
} }
var txtFile = getTextFile(fn, server); if (isScriptFilename(fn)) {
//Read from script
let script = workerScript.getScriptOnServer(fn);
if (script == null) {
return "";
}
return script.code;
} else {
//Read from text file
let txtFile = getTextFile(fn, server);
if (txtFile !== null) { if (txtFile !== null) {
return txtFile.text; return txtFile.text;
} else { } else {
return ""; return "";
} }
}
} else { } else {
throw makeRuntimeRejectMsg(workerScript, "Invalid argument passed in for read(): " + port); throw makeRuntimeRejectMsg(workerScript, "Invalid argument passed in for read(): " + port);
} }

@ -57,7 +57,7 @@ WorkerScript.prototype.getServer = function() {
//Returns the Script object for the underlying script //Returns the Script object for the underlying script
WorkerScript.prototype.getScript = function() { WorkerScript.prototype.getScript = function() {
let server = this.getServer(); let server = this.getServer();
for (var i = 0; i < server.scripts.length; ++i) { for (let i = 0; i < server.scripts.length; ++i) {
if (server.scripts[i].filename === this.name) { if (server.scripts[i].filename === this.name) {
return server.scripts[i]; return server.scripts[i];
} }
@ -66,6 +66,19 @@ WorkerScript.prototype.getScript = function() {
return null; return null;
} }
//Returns the Script object for the specified script
WorkerScript.prototype.getScriptOnServer = function(fn, server) {
if (server == null) {
server = this.getServer();
}
for (let i = 0; i < server.scripts.length; ++i) {
if (server.scripts[i].filename === fn) {
return server.scripts[i];
}
}
return null;
}
WorkerScript.prototype.shouldLog = function(fn) { WorkerScript.prototype.shouldLog = function(fn) {
return (this.disableLogs.ALL == null && this.disableLogs[fn] == null); return (this.disableLogs.ALL == null && this.disableLogs[fn] == null);
} }

@ -2,6 +2,7 @@ import {Augmentations, applyAugmentation,
AugmentationNames, AugmentationNames,
PlayerOwnedAugmentation} from "./Augmentations"; PlayerOwnedAugmentation} from "./Augmentations";
import {BitNodeMultipliers} from "./BitNodeMultipliers"; import {BitNodeMultipliers} from "./BitNodeMultipliers";
import {CodingContractRewardType} from "./CodingContracts";
import {Company, Companies, getNextCompanyPosition, import {Company, Companies, getNextCompanyPosition,
getJobRequirementText, CompanyPosition, getJobRequirementText, CompanyPosition,
CompanyPositions} from "./Company"; CompanyPositions} from "./Company";
@ -2278,6 +2279,52 @@ PlayerObject.prototype.queueAugmentation = function(name) {
this.queuedAugmentations.push(new PlayerOwnedAugmentation(name)); this.queuedAugmentations.push(new PlayerOwnedAugmentation(name));
} }
/************* Coding Contracts **************/
PlayerObject.prototype.gainCodingContractReward = function(reward, difficulty=1) {
if (reward == null || reward.type == null || reward == null) {
return `No reward for this contract`;
}
/* eslint-disable no-case-declarations */
switch (reward.type) {
case CodingContractRewardType.FactionReputation:
if (reward.name == null || !(Factions[reward.name] instanceof Faction)) {
// If no/invalid faction was designated, just give rewards to all factions
reward.type = CodingContractRewardType.FactionReputationAll;
return this.gainCodingContractReward(reward);
}
var repGain = CONSTANTS.CodingContractBaseFactionRepGain * difficulty;
Factions[reward.name].playerReputation += repGain;
return `Gained ${repGain} faction reputation for ${reward.name}`;
case CodingContractRewardType.FactionReputationAll:
const totalGain = CONSTANTS.CodingContractBaseFactionRepGain * difficulty;
const gainPerFaction = Math.floor(totalGain / this.factions.length);
for (const facName of this.factions) {
if (!(Factions[facName] instanceof Faction)) { continue; }
Factions[facName].playerReputation += gainPerFaction;
}
return `Gained ${gainPerFaction} reputation for each faction you are a member of`;
break;
case CodingContractRewardType.CompanyReputation:
if (reward.name == null || !(Companies[reward.name] instanceof Company)) {
//If no/invalid company was designated, just give rewards to all factions
reward.type = CodingContractRewardType.FactionReputationAll;
return this.gainCodingContractReward(reward);
}
var repGain = CONSTANTS.CodingContractBaseCompanyRepGain * difficulty;
Companies[reward.name].playerReputation += repGain;
return `Gained ${repGain} company reputation for ${reward.name}`;
break;
case CodingContractRewardType.Money:
default:
var moneyGain = CONSTANTS.CodingContractBaseMoneyGain * difficulty;
this.gainMoney(moneyGain);
return `Gained ${numeralWrapper.format(moneyGain, '$0.000a')}`;
break;
}
/* eslint-enable no-case-declarations */
}
/* Functions for saving and loading the Player data */ /* Functions for saving and loading the Player data */
function loadPlayer(saveString) { function loadPlayer(saveString) {
Player = JSON.parse(saveString, Reviver); Player = JSON.parse(saveString, Reviver);

@ -371,12 +371,13 @@ function checkValidFilename(filename) {
return false; return false;
} }
function Script() { function Script(fn = "", code = "", server = "") {
this.filename = ""; this.filename = fn;
this.code = ""; this.code = code;
this.ramUsage = 0; this.ramUsage = 0;
this.server = ""; //IP of server this script is on this.server = server; //IP of server this script is on
this.module = ""; this.module = "";
if (this.code !== "") {this.updateRamUsage();}
}; };
//Get the script data from the Script Editor and save it to the object //Get the script data from the Script Editor and save it to the object

@ -1,4 +1,5 @@
import {BitNodeMultipliers} from "./BitNodeMultipliers"; import {BitNodeMultipliers} from "./BitNodeMultipliers";
import {CodingContract, ContractTypes} from "./CodingContracts";
import {CONSTANTS} from "./Constants"; import {CONSTANTS} from "./Constants";
import {Programs} from "./CreateProgram"; import {Programs} from "./CreateProgram";
import {Player} from "./Player"; import {Player} from "./Player";
@ -42,6 +43,7 @@ function Server(params={ip:createRandomIp(), hostname:""}) {
this.programs = []; this.programs = [];
this.messages = []; this.messages = [];
this.textFiles = []; this.textFiles = [];
this.contracts = [];
this.dir = 0; //new Directory(this, null, ""); TODO this.dir = 0; //new Directory(this, null, ""); TODO
/* Hacking information (only valid for "foreign" aka non-purchased servers) */ /* Hacking information (only valid for "foreign" aka non-purchased servers) */
@ -113,6 +115,32 @@ Server.prototype.weaken = function(amt) {
this.capDifficulty(); this.capDifficulty();
} }
// Coding Contracts
Server.prototype.addContract = function(contract) {
this.contracts.push(contract);
}
Server.prototype.removeContract = function(contract) {
if (contract instanceof CodingContract) {
this.contracts = this.contracts.filter((c) => {
return c.fn !== contract.fn;
});
} else {
this.contracts = this.contracts.filter((c) => {
return c.fn !== contract;
});
}
}
Server.prototype.getContract = function(contractName) {
for (const contract of this.contracts) {
if (contract.fn === contractName) {
return contract;
}
}
return null;
}
//Functions for loading and saving a Server //Functions for loading and saving a Server
Server.prototype.toJSON = function() { Server.prototype.toJSON = function() {
return Generic_toJSON("Server", this); return Generic_toJSON("Server", this);

@ -2,6 +2,8 @@ import {substituteAliases, printAliases,
parseAliasDeclaration, parseAliasDeclaration,
removeAlias, GlobalAliases, removeAlias, GlobalAliases,
Aliases} from "./Alias"; Aliases} from "./Alias";
import {CodingContract, CodingContractResult,
CodingContractRewardType} from "./CodingContracts";
import {CONSTANTS} from "./Constants"; import {CONSTANTS} from "./Constants";
import {Programs} from "./CreateProgram"; import {Programs} from "./CreateProgram";
import {executeDarkwebTerminalCommand, import {executeDarkwebTerminalCommand,
@ -62,7 +64,7 @@ $(document).keydown(function(event) {
//Terminal //Terminal
if (routing.isOn(Page.Terminal)) { if (routing.isOn(Page.Terminal)) {
var terminalInput = document.getElementById("terminal-input-text-box"); var terminalInput = document.getElementById("terminal-input-text-box");
if (terminalInput != null && !event.ctrlKey && !event.shiftKey) {terminalInput.focus();} if (terminalInput != null && !event.ctrlKey && !event.shiftKey && !Terminal.contractOpen) {terminalInput.focus();}
if (event.keyCode === KEY.ENTER) { if (event.keyCode === KEY.ENTER) {
event.preventDefault(); //Prevent newline from being entered in Script Editor event.preventDefault(); //Prevent newline from being entered in Script Editor
@ -251,7 +253,7 @@ $(document).keydown(function(e) {
terminalCtrlPressed = true; terminalCtrlPressed = true;
} else if (e.shiftKey) { } else if (e.shiftKey) {
shiftKeyPressed = true; shiftKeyPressed = true;
} else if (terminalCtrlPressed || shiftKeyPressed) { } else if (terminalCtrlPressed || shiftKeyPressed || Terminal.contractOpen) {
//Don't focus //Don't focus
} else { } else {
var inputTextBox = document.getElementById("terminal-input-text-box"); var inputTextBox = document.getElementById("terminal-input-text-box");
@ -523,6 +525,8 @@ let Terminal = {
commandHistory: [], commandHistory: [],
commandHistoryIndex: 0, commandHistoryIndex: 0,
contractOpen: false, //True if a Coding Contract prompt is opened
resetTerminalInput: function() { resetTerminalInput: function() {
if (FconfSettings.WRAP_INPUT) { if (FconfSettings.WRAP_INPUT) {
document.getElementById("terminal-input-td").innerHTML = document.getElementById("terminal-input-td").innerHTML =
@ -1354,9 +1358,11 @@ let Terminal = {
} }
//Check if its a script or just a program/executable //Check if its a script or just a program/executable
//if (isScriptFilename(executableName)) { //Dont use isScriptFilename here because `executableName` includes the args too
if (executableName.includes(".script") || executableName.includes(".js") || executableName.includes(".ns")) { if (executableName.includes(".script") || executableName.includes(".js") || executableName.includes(".ns")) {
Terminal.runScript(executableName); Terminal.runScript(executableName);
} else if (executableName.endsWith(".cct")) {
Terminal.runContract(executableName);
} else { } else {
Terminal.runProgram(executableName); Terminal.runProgram(executableName);
} }
@ -1744,6 +1750,15 @@ let Terminal = {
allFiles.push(s.textFiles[i].fn); allFiles.push(s.textFiles[i].fn);
} }
} }
for (var i = 0; i < s.contracts.length; ++i) {
if (filter) {
if (s.contracts[i].fn.includes(filter)) {
allFiles.push(s.contracts[i].fn);
}
} else {
allFiles.push(s.contracts[i].fn);
}
}
//Sort the files alphabetically then print each //Sort the files alphabetically then print each
allFiles.sort(); allFiles.sort();
@ -1969,9 +1984,9 @@ let Terminal = {
post("Server base security level: " + targetServer.baseDifficulty); post("Server base security level: " + targetServer.baseDifficulty);
post("Server current security level: " + targetServer.hackDifficulty); post("Server current security level: " + targetServer.hackDifficulty);
post("Server growth rate: " + targetServer.serverGrowth); post("Server growth rate: " + targetServer.serverGrowth);
post("Netscript hack() execution time: " + numeralWrapper.format(scriptCalculateHackingTime(targetServer), '0.0') + "s"); post("Netscript hack() execution time: " + numeralWrapper.format(calculateHackingTime(targetServer), '0.0') + "s");
post("Netscript grow() execution time: " + numeralWrapper.format(scriptCalculateGrowTime(targetServer), '0.0') + "s"); post("Netscript grow() execution time: " + numeralWrapper.format(calculateGrowTime(targetServer), '0.0') + "s");
post("Netscript weaken() execution time: " + numeralWrapper.format(scriptCalculateWeakenTime(targetServer), '0.0') + "s"); post("Netscript weaken() execution time: " + numeralWrapper.format(calculateWeakenTime(targetServer), '0.0') + "s");
}; };
programHandlers[Programs.AutoLink.name] = () => { programHandlers[Programs.AutoLink.name] = () => {
post("This executable cannot be run."); post("This executable cannot be run.");
@ -2132,7 +2147,42 @@ let Terminal = {
post("ERROR: No such script"); post("ERROR: No such script");
},
runContract: async function(contractName) {
// There's already an opened contract
if (Terminal.contractOpen) {
return post("ERROR: There's already a Coding Contract in Progress");
} }
Terminal.contractOpen = true;
const serv = Player.getCurrentServer();
const contract = serv.getContract(contractName);
if (contract == null) {
return post("ERROR: No such contract");
}
const res = await contract.prompt();
switch (res) {
case CodingContractResult.Success:
var reward = Player.gainCodingContractReward(contract.reward, contract.getDifficulty());
post(`Contract SUCCESS - ${reward}`);
serv.removeContract(contract);
break;
case CodingContractResult.Failure:
post("Contract <p style='color:red;display:inline'>FAILED</p> - Contract is now self-destructing");
++contract.tries;
if (contract.tries >= contract.getMaxNumTries()) {
serv.removeContract(contract);
}
break;
case CodingContractResult.Cancelled:
default:
post("Contract cancelled");
break;
}
Terminal.contractOpen = false;
},
}; };
export {postNetburnerText, Terminal}; export {postNetburnerText, Terminal};

@ -0,0 +1,463 @@
import { getRandomInt } from "../../utils/helpers/getRandomInt";
/* tslint:disable:completed-docs no-magic-numbers arrow-return-shorthand */
/* Function that generates a valid 'data' for a contract type */
export type GeneratorFunc = () => any;
/* Function that checks if the provided solution is the correct one */
export type SolverFunc = (data: any, answer: string) => boolean;
/* Function that returns a string with the problem's description.
Requires the 'data' of a Contract as input */
export type DescriptionFunc = (data: any) => string;
export interface ICodingContractTypeMetadata {
desc: DescriptionFunc;
difficulty: number;
gen: GeneratorFunc;
name: string;
numTries: number;
solver: SolverFunc;
}
export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
{
desc: (n: number) => {
return ["A prime factor is a factor that is a prime number.",
`What is the largest prime factor of ${n}?`].join(" ");
},
difficulty: 1,
gen: () => {
return getRandomInt(500, 9e9);
},
name: "Find Largest Prime Factor",
numTries: 10,
solver: (data: number, ans: string) => {
let fac: number = 2;
let n: number = data;
while (n > fac) {
if (n % fac === 0) {
n = Math.round(n / fac);
fac = 2;
} else {
++fac;
}
}
return fac === parseInt(ans, 10);
},
},
{
desc: (n: number[]) => {
return ["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.",
`${n.toString()}`].join(" ");
},
difficulty: 1,
gen: () => {
const len: number = getRandomInt(5, 40);
const arr: number[] = [];
arr.length = len;
for (let i: number = 0; i < len; ++i) {
arr[i] = getRandomInt(-10, 10);
}
return arr;
},
name: "Subarray with Maximum Sum",
numTries: 10,
solver: (data: number[], ans: string) => {
const nums: number[] = data.slice();
for (let i: number = 1; i < nums.length; i++) {
nums[i] = Math.max(nums[i], nums[i] + nums[i - 1]);
}
return parseInt(ans, 10) === Math.max(...nums);
},
},
{
desc: (n: number) => {
return ["It is possible write four as a sum in exactly four different ways:\n\n",
" 3 + 1\n",
" 2 + 2\n",
" 2 + 1 + 1\n",
" 1 + 1 + 1 + 1\n\n",
`How many different ways can ${n} be written as a sum of at least`,
"two positive integers?"].join(" ");
},
difficulty: 1.5,
gen: () => {
return getRandomInt(8, 100);
},
name: "Total Ways to Sum",
numTries: 10,
solver: (data: number, ans: string) => {
const ways: number[] = [1];
ways.length = data + 1;
ways.fill(0, 1);
for (let i: number = 1; i < data; ++i) {
for (let j: number = i; j <= data; ++j) {
ways[j] += ways[j - i];
}
}
return ways[data] === parseInt(ans, 10);
},
},
{
desc: (n: number[][]) => {
let d: string = ["Given the following array of array of numbers representing a 2D matrix,",
"return the elements of the matrix as an array in spiral order:\n\n"].join(" ");
for (const line of n) {
d += `${line.toString()},\n`;
}
d += ["\nHere is an example of what spiral order should be:",
"\nExample:",
" [\n",
" [1, 2, 3],\n",
" [4, 5, 6],\n",
" [7, 8, 9]\n",
" ] should result in [1, 2, 3, 6, 9, 8 ,7, 4, 5]\n\n",
"Note that the matrix will not always be square:\n",
" [\n",
" [1, 2, 3, 4]\n",
" [5, 6, 7, 8]\n",
" [9, 10, 11, 12]\n",
" ] should result in [1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7"].join(" ");
return d;
},
difficulty: 2,
gen: () => {
const m: number = getRandomInt(1, 10);
const n: number = getRandomInt(1, 10);
const matrix: number[][] = [];
matrix.length = m;
for (let i: number = 0; i < m; ++i) {
matrix[i].length = n;
}
for (let i: number = 0; i < m; ++i) {
for (let j: number = 0; j < n; ++j) {
matrix[i][j] = getRandomInt(1, 50);
}
}
return matrix;
},
name: "Spiralize Matrix",
numTries: 10,
solver: (data: number[][], ans: string) => {
const spiral: number[] = [];
const m: number = data.length;
const n: number = data[0].length;
let u: number = 0;
let d: number = m - 1;
let l: number = 0;
let r: number = n - 1;
let k: number = 0;
while (true) {
// Up
for (let col: number = l; col <= r; col++) {
spiral[k] = data[u][col];
++k;
}
if (++u > d) { break; }
// Right
for (let row: number = u; row <= d; row++) {
spiral[k] = data[row][r];
++k;
}
if (--r < l) { break; }
// Down
for (let col: number = r; col >= l; col--) {
spiral[k] = data[d][col];
++k;
}
if (--d < u) { break; }
// Left
for (let row: number = d; row >= u; row--) {
spiral[k] = data[row][l];
++k;
}
if (++l > r) { break; }
}
const playerAns: any[] = ans.split(",");
for (let i: number = 0; i < playerAns.length; ++i) {
playerAns[i] = parseInt(playerAns[i], 10);
}
if (spiral.length !== playerAns.length) { return false; }
for (let i: number = 0; i < spiral.length; ++i) {
if (spiral[i] !== playerAns[i]) {
return false;
}
}
return true;
},
},
{
desc: (arr: number[]) => {
return ["You are given the following array of integers:\n\n",
`${arr}\n\n`,
"Each element in the array represents your maximum jump length",
"at that position. Assuming you are initially positioned",
"at the start of the array, determine whether you are",
"able to reach the last index exactly.\n\n",
"Your answer should be submitted as 1 or 0, representing true and false respectively"].join(" ");
},
difficulty: 2.5,
gen: () => {
const len: number = getRandomInt(1, 25);
const arr: number[] = [];
arr.length = len;
for (let i: number = 0; i < arr.length; ++i) {
arr[i] = getRandomInt(0, 24);
}
},
name: "Array Jumping Game",
numTries: 1,
solver: (data: number[], ans: string) => {
const n: number = data.length;
let i: number = 0;
for (let reach: number = 0; i < n && i <= reach; ++i) {
reach = Math.max(i + data[i], reach);
}
const solution: boolean = (i === n);
if (ans === "1" && solution) { return true; }
if (ans === "0" && !solution) { return true; }
return false;
},
},
{
desc: (arr: number[][]) => {
return ["Given the following array of array of numbers representing a list of",
"intervals, merge all overlapping intervals.\n\n",
`${arr}\n\n`,
"Example:\n\n",
"[[1, 3], [8, 10], [2, 6], [10, 16]]\n\n",
"would merge into [[1, 6], [8, 16]].\n\n",
"The intervals must be returned in ASCENDING order.",
"You can assume that in an interval, the first number will always be",
"smaller than the second."].join(" ");
},
difficulty: 3,
gen: () => {
const intervals: number[][] = [];
const numIntervals: number = getRandomInt(1, 15);
for (let i: number = 0; i < numIntervals; ++i) {
const start: number = getRandomInt(1, 25);
const end: number = start + getRandomInt(1, 10);
intervals.push([start, end]);
}
return intervals;
},
name: "Merge Overlapping Intervals",
numTries: 15,
solver: (data: number[][], ans: string) => {
const intervals: number[][] = data.slice();
intervals.sort((a: number[], b: number[]) => {
return a[0] - b[0];
});
const result: number[][] = [];
let start: number = intervals[0][0];
let end: number = intervals[0][1];
for (const interval of intervals) {
if (interval[0] <= end) {
end = Math.max(end, interval[1]);
} else {
result.push([start, end]);
start = interval[0];
end = interval[1];
}
}
result.push([start, end]);
const sanitizedResult: string = result
.toString()
.replace(/\s/g, "");
const sanitizedAns: string = ans.replace(/\s/g, "");
return sanitizedResult === sanitizedAns;
},
},
{
desc: (data: string) => {
return ["Given the following string containing only digits, determine",
"an array with all possible valid IP address combinations",
"that can be created from the string:\n\n",
`${data}\n\n`,
"Example:\n\n",
"'25525511135' -> ['255.255.11.135', '255.255.111.35']"].join(" ");
},
difficulty: 3,
gen: () => {
let str: string = "";
for (let i: number = 0; i < 4; ++i) {
const num: number = getRandomInt(0, 255);
const convNum: string = num.toString();
str += convNum;
}
return str;
},
name: "Generate IP Addresses",
numTries: 10,
solver: (data: string, ans: string) => {
const ret: string[] = [];
for (let a: number = 1; a <= 3; ++a) {
for (let b: number = 1; b <= 3; ++b) {
for (let c: number = 1; c <= 3; ++c) {
for (let d: number = 1; d <= 3; ++d) {
if (a + b + c + d === data.length) {
const A: number = parseInt(data.substring(0, a), 10);
const B: number = parseInt(data.substring(a, a + b), 10);
const C: number = parseInt(data.substring(a + b, a + b + c), 10);
const D: number = parseInt(data.substring(a + b + c, a + b + c + d), 10);
if (A <= 255 && B <= 255 && C <= 255 && D <= 255) {
const ip: string = [A.toString(), ".",
B.toString(), ".",
C.toString(), ".",
D.toString()].join("");
if (ip.length === data.length + 3) {
ret.push(ip);
}
}
}
}
}
}
}
let sanitizedAns: string = ans.replace(/\s/g, "");
if (sanitizedAns.length === 0 || sanitizedAns[0] !== "[" || sanitizedAns[sanitizedAns.length - 1] !== "]") {
return false;
}
sanitizedAns = sanitizedAns.slice(1, -1); // Remove []
const ansArr: string[] = sanitizedAns.split(",");
if (ansArr.length !== ret.length) { return false; }
for (const ipInAns of ansArr) {
if (!ret.includes(ipInAns)) { return false; }
}
return true;
},
},
{
desc: (data: number[]) => {
return ["You are given the following array of stock prices where the i-th element",
"represents the stock price on day i:\n\n",
`${data}\n\n`,
"Determine the maximum possible profit you can earn using at most",
"one transaction (i.e. you can only buy and sell the stock once). If no profit can be made",
"then the answer should be 0. Note",
"that you have to buy the stock before you can sell it"].join(" ");
},
difficulty: 1,
gen: () => {
const len: number = getRandomInt(1, 50);
const arr: number[] = [];
arr.length = len;
for (let i: number = 0; i < len; ++i) {
arr[i] = getRandomInt(1, 200);
}
return arr;
},
name: "Algorithmic Stock Trader I",
numTries: 5,
solver: (data: number[], ans: string) => {
let maxCur: number = 0;
let maxSoFar: number = 0;
for (let i: number = 1; i < data.length; ++i) {
maxCur = Math.max(0, maxCur += data[i] - data[i - 1]);
maxSoFar = Math.max(maxCur, maxSoFar);
}
return maxSoFar.toString() === ans;
},
},
{
desc: (data: number[]) => {
return ["You are given the following array of stock prices where the i-th element",
"represents the stock price on day i:\n\n",
`${data}\n\n`,
"Determine the maximum possible profit you can earn using as many",
"transactions as you'd like. A transaction is defined as buying",
"and then selling one share of the stock. Note that you cannot",
"engage in multiple transactions at once. In other words, you",
"must sell the stock before you buy it again.\n\n",
"If no profit can be made, then the answer should be 0"].join(" ");
},
difficulty: 2,
gen: () => {
const len: number = getRandomInt(1, 50);
const arr: number[] = [];
arr.length = len;
for (let i: number = 0; i < len; ++i) {
arr[i] = getRandomInt(1, 200);
}
return arr;
},
name: "Algorithmic Stock Trader II",
numTries: 10,
solver: (data: number[], ans: string) => {
let profit: number = 0;
for (let p: number = 1; p < data.length; ++p) {
profit += Math.max(data[p] - data[p - 1], 0);
}
return profit.toString() === ans;
},
},
{
desc: (data: number[]) => {
return ["You are given the following array of stock prices where the i-th element",
"represents the stock price on day i:\n\n",
`${data}\n\n`,
"Determine the maximum possible profit you can earn using at most ",
"two transactions. A transaction is defined as buying",
"and then selling one share of the stock. Note that you cannot",
"engage in multiple transactions at once. In other words, you",
"must sell the stock before you buy it again.\n\n",
"If no profit can be made, then the answer should be 0"].join(" ");
},
difficulty: 5,
gen: () => {
const len: number = getRandomInt(1, 50);
const arr: number[] = [];
arr.length = len;
for (let i: number = 0; i < len; ++i) {
arr[i] = getRandomInt(1, 200);
}
return arr;
},
name: "Algorithmic Stock Trader III",
numTries: 10,
solver: (data: number[], ans: string) => {
let hold1: number = Number.MIN_SAFE_INTEGER;
let hold2: number = Number.MIN_SAFE_INTEGER;
let release1: number = 0;
let release2: number = 0;
for (const price of data) {
release2 = Math.max(release2, hold2 + price);
hold2 = Math.max(hold2, release1 - price);
release1 = Math.max(release1, hold1 + price);
hold1 = Math.max(hold1, price * -1);
}
return release2.toString() === ans;
},
},
];

@ -150,7 +150,7 @@ export const serverMetadata: IServerMetadata[] = [
literature: ["beyond-man.lit"], literature: ["beyond-man.lit"],
maxRamExponent: { maxRamExponent: {
max: 9, max: 9,
min: 5 min: 5,
}, },
moneyAvailable: { moneyAvailable: {
max: 40e9, max: 40e9,
@ -226,7 +226,7 @@ export const serverMetadata: IServerMetadata[] = [
], ],
maxRamExponent: { maxRamExponent: {
max: 9, max: 9,
min: 7 min: 7,
}, },
moneyAvailable: { moneyAvailable: {
max: 22e9, max: 22e9,
@ -297,7 +297,7 @@ export const serverMetadata: IServerMetadata[] = [
literature: ["simulated-reality.lit"], literature: ["simulated-reality.lit"],
maxRamExponent: { maxRamExponent: {
max: 11, max: 11,
min: 7 min: 7,
}, },
moneyAvailable: { moneyAvailable: {
max: 1800e6, max: 1800e6,
@ -404,7 +404,7 @@ export const serverMetadata: IServerMetadata[] = [
literature: ["beyond-man.lit"], literature: ["beyond-man.lit"],
maxRamExponent: { maxRamExponent: {
max: 8, max: 8,
min: 5 min: 5,
}, },
moneyAvailable: { moneyAvailable: {
max: 750e6, max: 750e6,
@ -431,7 +431,7 @@ export const serverMetadata: IServerMetadata[] = [
literature: ["A-Green-Tomorrow.lit"], literature: ["A-Green-Tomorrow.lit"],
maxRamExponent: { maxRamExponent: {
max: 7, max: 7,
min: 4 min: 4,
}, },
moneyAvailable: { moneyAvailable: {
max: 800e6, max: 800e6,
@ -479,7 +479,7 @@ export const serverMetadata: IServerMetadata[] = [
hostname: "univ-energy", hostname: "univ-energy",
maxRamExponent: { maxRamExponent: {
max: 7, max: 7,
min: 4 min: 4,
}, },
moneyAvailable: { moneyAvailable: {
max: 1200e6, max: 1200e6,
@ -506,7 +506,7 @@ export const serverMetadata: IServerMetadata[] = [
literature: ["coded-intelligence.lit"], literature: ["coded-intelligence.lit"],
maxRamExponent: { maxRamExponent: {
max: 7, max: 7,
min: 4 min: 4,
}, },
moneyAvailable: { moneyAvailable: {
max: 900000000, max: 900000000,
@ -533,7 +533,7 @@ export const serverMetadata: IServerMetadata[] = [
literature: ["synthetic-muscles.lit"], literature: ["synthetic-muscles.lit"],
maxRamExponent: { maxRamExponent: {
max: 6, max: 6,
min: 4 min: 4,
}, },
moneyAvailable: { moneyAvailable: {
max: 700000000, max: 700000000,
@ -631,7 +631,7 @@ export const serverMetadata: IServerMetadata[] = [
literature: ["history-of-synthoids.lit"], literature: ["history-of-synthoids.lit"],
maxRamExponent: { maxRamExponent: {
max: 6, max: 6,
min: 4 min: 4,
}, },
moneyAvailable: { moneyAvailable: {
max: 1000000000, max: 1000000000,
@ -706,7 +706,7 @@ export const serverMetadata: IServerMetadata[] = [
], ],
maxRamExponent: { maxRamExponent: {
max: 7, max: 7,
min: 4 min: 4,
}, },
moneyAvailable: { moneyAvailable: {
max: 900000000, max: 900000000,
@ -755,7 +755,7 @@ export const serverMetadata: IServerMetadata[] = [
literature: ["A-Green-Tomorrow.lit"], literature: ["A-Green-Tomorrow.lit"],
maxRamExponent: { maxRamExponent: {
max: 6, max: 6,
min: 3 min: 3,
}, },
moneyAvailable: { moneyAvailable: {
max: 1750000000, max: 1750000000,
@ -825,7 +825,7 @@ export const serverMetadata: IServerMetadata[] = [
hostname: "unitalife", hostname: "unitalife",
maxRamExponent: { maxRamExponent: {
max: 6, max: 6,
min: 4 min: 4,
}, },
moneyAvailable: { moneyAvailable: {
max: 1100000000, max: 1100000000,
@ -851,7 +851,7 @@ export const serverMetadata: IServerMetadata[] = [
hostname: "lexo-corp", hostname: "lexo-corp",
maxRamExponent: { maxRamExponent: {
max: 7, max: 7,
min: 4 min: 4,
}, },
moneyAvailable: { moneyAvailable: {
max: 800000000, max: 800000000,
@ -877,7 +877,7 @@ export const serverMetadata: IServerMetadata[] = [
hostname: "rho-construction", hostname: "rho-construction",
maxRamExponent: { maxRamExponent: {
max: 6, max: 6,
min: 4 min: 4,
}, },
moneyAvailable: { moneyAvailable: {
max: 700000000, max: 700000000,
@ -904,7 +904,7 @@ export const serverMetadata: IServerMetadata[] = [
literature: ["sector-12-crime.lit"], literature: ["sector-12-crime.lit"],
maxRamExponent: { maxRamExponent: {
max: 7, max: 7,
min: 4 min: 4,
}, },
moneyAvailable: { moneyAvailable: {
max: 750000000, max: 750000000,
@ -930,7 +930,7 @@ export const serverMetadata: IServerMetadata[] = [
hostname: "aevum-police", hostname: "aevum-police",
maxRamExponent: { maxRamExponent: {
max: 6, max: 6,
min: 4 min: 4,
}, },
moneyAvailable: { moneyAvailable: {
max: 400000000, max: 400000000,
@ -961,7 +961,7 @@ export const serverMetadata: IServerMetadata[] = [
], ],
maxRamExponent: { maxRamExponent: {
max: 7, max: 7,
min: 4 min: 4,
}, },
moneyAvailable: { moneyAvailable: {
max: 250000000, max: 250000000,
@ -987,7 +987,7 @@ export const serverMetadata: IServerMetadata[] = [
hostname: "zb-institute", hostname: "zb-institute",
maxRamExponent: { maxRamExponent: {
max: 7, max: 7,
min: 4 min: 4,
}, },
moneyAvailable: { moneyAvailable: {
max: 1100000000, max: 1100000000,
@ -1018,7 +1018,7 @@ export const serverMetadata: IServerMetadata[] = [
], ],
maxRamExponent: { maxRamExponent: {
max: 6, max: 6,
min: 4 min: 4,
}, },
moneyAvailable: { moneyAvailable: {
max: 350000000, max: 350000000,
@ -1067,7 +1067,7 @@ export const serverMetadata: IServerMetadata[] = [
literature: ["tensions-in-tech-race.lit"], literature: ["tensions-in-tech-race.lit"],
maxRamExponent: { maxRamExponent: {
max: 7, max: 7,
min: 4 min: 4,
}, },
moneyAvailable: { moneyAvailable: {
max: 550000000, max: 550000000,
@ -1093,7 +1093,7 @@ export const serverMetadata: IServerMetadata[] = [
hostname: "the-hub", hostname: "the-hub",
maxRamExponent: { maxRamExponent: {
max: 6, max: 6,
min: 3 min: 3,
}, },
moneyAvailable: { moneyAvailable: {
max: 200000000, max: 200000000,
@ -1143,7 +1143,7 @@ export const serverMetadata: IServerMetadata[] = [
literature: ["simulated-reality.lit"], literature: ["simulated-reality.lit"],
maxRamExponent: { maxRamExponent: {
max: 7, max: 7,
min: 4 min: 4,
}, },
moneyAvailable: 275000000, moneyAvailable: 275000000,
networkLayer: 4, networkLayer: 4,
@ -1370,7 +1370,7 @@ export const serverMetadata: IServerMetadata[] = [
hostname: "millenium-fitness", hostname: "millenium-fitness",
maxRamExponent: { maxRamExponent: {
max: 8, max: 8,
min: 4 min: 4,
}, },
moneyAvailable: 250000000, moneyAvailable: 250000000,
networkLayer: 6, networkLayer: 6,
@ -1393,7 +1393,7 @@ export const serverMetadata: IServerMetadata[] = [
hostname: "powerhouse-fitness", hostname: "powerhouse-fitness",
maxRamExponent: { maxRamExponent: {
max: 6, max: 6,
min: 4 min: 4,
}, },
moneyAvailable: 900000000, moneyAvailable: 900000000,
networkLayer: 14, networkLayer: 14,
@ -1436,7 +1436,7 @@ export const serverMetadata: IServerMetadata[] = [
], ],
maxRamExponent: { maxRamExponent: {
max: 9, max: 9,
min: 5 min: 5,
}, },
moneyAvailable: 0, moneyAvailable: 0,
networkLayer: 11, networkLayer: 11,
@ -1455,7 +1455,7 @@ export const serverMetadata: IServerMetadata[] = [
literature: ["democracy-is-dead.lit"], literature: ["democracy-is-dead.lit"],
maxRamExponent: { maxRamExponent: {
max: 8, max: 8,
min: 4 min: 4,
}, },
moneyAvailable: 0, moneyAvailable: 0,
networkLayer: 5, networkLayer: 5,
@ -1474,7 +1474,7 @@ export const serverMetadata: IServerMetadata[] = [
literature: ["democracy-is-dead.lit"], literature: ["democracy-is-dead.lit"],
maxRamExponent: { maxRamExponent: {
max: 7, max: 7,
min: 4 min: 4,
}, },
moneyAvailable: 0, moneyAvailable: 0,
networkLayer: 4, networkLayer: 4,

@ -1,5 +1,6 @@
{ {
"compilerOptions": { "compilerOptions": {
"lib" : ["es2016", "dom"],
"module": "commonjs", "module": "commonjs",
"target": "es6", "target": "es6",
"sourceMap": true, "sourceMap": true,

@ -17,7 +17,7 @@ function infiltrationBoxClose() {
function infiltrationBoxOpen() { function infiltrationBoxOpen() {
var box = document.getElementById("infiltration-box-container"); var box = document.getElementById("infiltration-box-container");
box.style.display = "block"; box.style.display = "flex";
} }
function infiltrationSetText(txt) { function infiltrationSetText(txt) {