diff --git a/src/CodingContracts.ts b/src/CodingContracts.ts new file mode 100644 index 000000000..c4c8d661f --- /dev/null +++ b/src/CodingContracts.ts @@ -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 = {}; + +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 { + // tslint:disable-next-line + return new Promise((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; diff --git a/src/Constants.js b/src/Constants.js index c526c3610..1c04f5217 100644 --- a/src/Constants.js +++ b/src/Constants.js @@ -85,6 +85,8 @@ let CONSTANTS = { ScriptGetScriptRamCost: 0.1, ScriptGetHackTimeRamCost: 0.05, ScriptGetFavorToDonate: 0.10, + ScriptGetContractDataRamCost: 25, + ScriptAttemptContractRamCost: 25, ScriptSingularityFn1RamCost: 1, ScriptSingularityFn2RamCost: 2, @@ -191,12 +193,13 @@ let CONSTANTS = { "-Nodes slowly regenerate health over time.", - //Gang constants + /* Gang constant */ GangRespectToReputationRatio: 2, //Respect is divided by this to get rep gain MaximumGangMembers: 20, GangRecruitCostMultiplier: 2, GangTerritoryUpdateTimer: 150, + /* Time Constants */ MillisecondsPer20Hours: 72000000, GameCyclesPer20Hours: 72000000 / 200, @@ -224,6 +227,7 @@ let CONSTANTS = { MillisecondsPerFiveMinutes: 300000, GameCyclesPerFiveMinutes: 300000 / 200, + /* Player Work / Action related Constants */ FactionWorkHacking: "Faction Hacking Work", FactionWorkField: "Faction Field Work", FactionWorkSecurity: "Faction Security Work", @@ -267,6 +271,11 @@ let CONSTANTS = { CrimeAssassination: "assassinate a high-profile target", CrimeHeist: "pull off the ultimate heist", + /* Coding Contract Constants */ + CodingContractBaseFactionRepGain: 2500, + CodingContractBaseCompanyRepGain: 4000, + CodingContractBaseMoneyGain: 10e6, + /* Tutorial related things */ 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 " + @@ -497,7 +506,13 @@ let CONSTANTS = { "World Stock Exchange account and TIX API Access
", LatestUpdate: - `v0.40.3
+ ` + v0.40.4
+ * (TODO NEEDS DOCUMENTATION) The write() and read() Netscript functions now work on scripts
+ * It is now possible to use freely use angled bracket (<, >) and create DOM elements using tprint()
+ * Added Coding Contracts (not yet generated in game, but the data/implementation exists)
+ + v0.40.3
-----------------------------------------------
* Bladeburner Changes:
*** Increased the effect that agi and dexterity have on action time
diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index 8ce9e5d6a..19c79d8c3 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -475,13 +475,6 @@ function NetscriptFunctions(workerScript) { throw makeRuntimeRejectMsg(workerScript, "tprint() call has incorrect number of arguments. Takes 1 argument"); } 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:

" + - "DON'T USE TPRINT() TO OUTPUT HTML ELEMENTS TO YOUR TERMINAL!!!!

" + - "(You lost 1 HP)"); - return; - } post(workerScript.scriptRef.filename + ": " + args.toString()); }, clearLog : function() { @@ -1839,21 +1832,35 @@ function NetscriptFunctions(workerScript) { throw makeRuntimeRejectMsg(workerScript, "Could not find port: " + port + ". This is a bug contact the game developer"); } 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 server = getServer(workerScript.serverIp); + var server = workerScript.getServer(); if (server == null) { 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 (txtFile == null) { - txtFile = createTextFile(fn, data, server); - return true; - } - if (mode === "w") { - txtFile.write(data); + 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 { - txtFile.append(data); + //Write to text file + let txtFile = getTextFile(fn, server); + if (txtFile == null) { + txtFile = createTextFile(fn, data, server); + return true; + } + if (mode === "w") { + txtFile.write(data); + } else { + txtFile.append(data); + } } return true; } else { @@ -1895,17 +1902,27 @@ function NetscriptFunctions(workerScript) { throw makeRuntimeRejectMsg(workerScript, "ERROR: Could not find port: " + port + ". This is a bug contact the game developer"); } return port.read(); - } else if (isString(port)) { //Read from text file - var fn = port; - var server = getServer(workerScript.serverIp); + } else if (isString(port)) { //Read from script or text file + let fn = port; + let server = getServer(workerScript.serverIp); if (server == null) { 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 (txtFile !== null) { - return txtFile.text; + if (isScriptFilename(fn)) { + //Read from script + let script = workerScript.getScriptOnServer(fn); + if (script == null) { + return ""; + } + return script.code; } else { - return ""; + //Read from text file + let txtFile = getTextFile(fn, server); + if (txtFile !== null) { + return txtFile.text; + } else { + return ""; + } } } else { throw makeRuntimeRejectMsg(workerScript, "Invalid argument passed in for read(): " + port); diff --git a/src/NetscriptWorker.js b/src/NetscriptWorker.js index aeb4ccf9c..0fbe82041 100644 --- a/src/NetscriptWorker.js +++ b/src/NetscriptWorker.js @@ -57,7 +57,7 @@ WorkerScript.prototype.getServer = function() { //Returns the Script object for the underlying script WorkerScript.prototype.getScript = function() { 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) { return server.scripts[i]; } @@ -66,6 +66,19 @@ WorkerScript.prototype.getScript = function() { 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) { return (this.disableLogs.ALL == null && this.disableLogs[fn] == null); } diff --git a/src/Player.js b/src/Player.js index 9d36ddc59..39516f995 100644 --- a/src/Player.js +++ b/src/Player.js @@ -2,6 +2,7 @@ import {Augmentations, applyAugmentation, AugmentationNames, PlayerOwnedAugmentation} from "./Augmentations"; import {BitNodeMultipliers} from "./BitNodeMultipliers"; +import {CodingContractRewardType} from "./CodingContracts"; import {Company, Companies, getNextCompanyPosition, getJobRequirementText, CompanyPosition, CompanyPositions} from "./Company"; @@ -2278,6 +2279,52 @@ PlayerObject.prototype.queueAugmentation = function(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 */ function loadPlayer(saveString) { Player = JSON.parse(saveString, Reviver); diff --git a/src/Script.js b/src/Script.js index cc35e6cc7..9cfbb58f4 100755 --- a/src/Script.js +++ b/src/Script.js @@ -371,12 +371,13 @@ function checkValidFilename(filename) { return false; } -function Script() { - this.filename = ""; - this.code = ""; +function Script(fn = "", code = "", server = "") { + this.filename = fn; + this.code = code; this.ramUsage = 0; - this.server = ""; //IP of server this script is on + this.server = server; //IP of server this script is on this.module = ""; + if (this.code !== "") {this.updateRamUsage();} }; //Get the script data from the Script Editor and save it to the object diff --git a/src/Server.js b/src/Server.js index ce007fd54..35e8d943b 100644 --- a/src/Server.js +++ b/src/Server.js @@ -1,4 +1,5 @@ import {BitNodeMultipliers} from "./BitNodeMultipliers"; +import {CodingContract, ContractTypes} from "./CodingContracts"; import {CONSTANTS} from "./Constants"; import {Programs} from "./CreateProgram"; import {Player} from "./Player"; @@ -37,11 +38,12 @@ function Server(params={ip:createRandomIp(), hostname:""}) { this.ramUsed = 0; this.cpuCores = 1; //Max of 8, affects hacking times and Hacking Mission starting Cores - this.scripts = []; - this.runningScripts = []; //Stores RunningScript objects - this.programs = []; - this.messages = []; - this.textFiles = []; + this.scripts = []; + this.runningScripts = []; //Stores RunningScript objects + this.programs = []; + this.messages = []; + this.textFiles = []; + this.contracts = []; this.dir = 0; //new Directory(this, null, ""); TODO /* Hacking information (only valid for "foreign" aka non-purchased servers) */ @@ -113,6 +115,32 @@ Server.prototype.weaken = function(amt) { 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 Server.prototype.toJSON = function() { return Generic_toJSON("Server", this); diff --git a/src/Terminal.js b/src/Terminal.js index e58f5aae7..7813c4d71 100644 --- a/src/Terminal.js +++ b/src/Terminal.js @@ -1,9 +1,11 @@ import {substituteAliases, printAliases, parseAliasDeclaration, removeAlias, GlobalAliases, - Aliases} from "./Alias"; -import {CONSTANTS} from "./Constants"; -import {Programs} from "./CreateProgram"; + Aliases} from "./Alias"; +import {CodingContract, CodingContractResult, + CodingContractRewardType} from "./CodingContracts"; +import {CONSTANTS} from "./Constants"; +import {Programs} from "./CreateProgram"; import {executeDarkwebTerminalCommand, checkIfConnectedToDarkweb, DarkWebItems} from "./DarkWeb"; @@ -62,7 +64,7 @@ $(document).keydown(function(event) { //Terminal if (routing.isOn(Page.Terminal)) { 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) { event.preventDefault(); //Prevent newline from being entered in Script Editor @@ -251,7 +253,7 @@ $(document).keydown(function(e) { terminalCtrlPressed = true; } else if (e.shiftKey) { shiftKeyPressed = true; - } else if (terminalCtrlPressed || shiftKeyPressed) { + } else if (terminalCtrlPressed || shiftKeyPressed || Terminal.contractOpen) { //Don't focus } else { var inputTextBox = document.getElementById("terminal-input-text-box"); @@ -523,6 +525,8 @@ let Terminal = { commandHistory: [], commandHistoryIndex: 0, + contractOpen: false, //True if a Coding Contract prompt is opened + resetTerminalInput: function() { if (FconfSettings.WRAP_INPUT) { document.getElementById("terminal-input-td").innerHTML = @@ -1354,9 +1358,11 @@ let Terminal = { } //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")) { Terminal.runScript(executableName); + } else if (executableName.endsWith(".cct")) { + Terminal.runContract(executableName); } else { Terminal.runProgram(executableName); } @@ -1744,6 +1750,15 @@ let Terminal = { 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 allFiles.sort(); @@ -1969,9 +1984,9 @@ let Terminal = { post("Server base security level: " + targetServer.baseDifficulty); post("Server current security level: " + targetServer.hackDifficulty); post("Server growth rate: " + targetServer.serverGrowth); - post("Netscript hack() execution time: " + numeralWrapper.format(scriptCalculateHackingTime(targetServer), '0.0') + "s"); - post("Netscript grow() execution time: " + numeralWrapper.format(scriptCalculateGrowTime(targetServer), '0.0') + "s"); - post("Netscript weaken() execution time: " + numeralWrapper.format(scriptCalculateWeakenTime(targetServer), '0.0') + "s"); + post("Netscript hack() execution time: " + numeralWrapper.format(calculateHackingTime(targetServer), '0.0') + "s"); + post("Netscript grow() execution time: " + numeralWrapper.format(calculateGrowTime(targetServer), '0.0') + "s"); + post("Netscript weaken() execution time: " + numeralWrapper.format(calculateWeakenTime(targetServer), '0.0') + "s"); }; programHandlers[Programs.AutoLink.name] = () => { post("This executable cannot be run."); @@ -2132,7 +2147,42 @@ let Terminal = { 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

FAILED

- 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}; diff --git a/src/data/codingcontracttypes.ts b/src/data/codingcontracttypes.ts new file mode 100644 index 000000000..786e6ca6f --- /dev/null +++ b/src/data/codingcontracttypes.ts @@ -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; + }, + }, +]; diff --git a/src/data/servers.ts b/src/data/servers.ts index cc6cf82ef..12698a3b8 100644 --- a/src/data/servers.ts +++ b/src/data/servers.ts @@ -150,7 +150,7 @@ export const serverMetadata: IServerMetadata[] = [ literature: ["beyond-man.lit"], maxRamExponent: { max: 9, - min: 5 + min: 5, }, moneyAvailable: { max: 40e9, @@ -226,7 +226,7 @@ export const serverMetadata: IServerMetadata[] = [ ], maxRamExponent: { max: 9, - min: 7 + min: 7, }, moneyAvailable: { max: 22e9, @@ -297,7 +297,7 @@ export const serverMetadata: IServerMetadata[] = [ literature: ["simulated-reality.lit"], maxRamExponent: { max: 11, - min: 7 + min: 7, }, moneyAvailable: { max: 1800e6, @@ -404,7 +404,7 @@ export const serverMetadata: IServerMetadata[] = [ literature: ["beyond-man.lit"], maxRamExponent: { max: 8, - min: 5 + min: 5, }, moneyAvailable: { max: 750e6, @@ -431,7 +431,7 @@ export const serverMetadata: IServerMetadata[] = [ literature: ["A-Green-Tomorrow.lit"], maxRamExponent: { max: 7, - min: 4 + min: 4, }, moneyAvailable: { max: 800e6, @@ -479,7 +479,7 @@ export const serverMetadata: IServerMetadata[] = [ hostname: "univ-energy", maxRamExponent: { max: 7, - min: 4 + min: 4, }, moneyAvailable: { max: 1200e6, @@ -506,7 +506,7 @@ export const serverMetadata: IServerMetadata[] = [ literature: ["coded-intelligence.lit"], maxRamExponent: { max: 7, - min: 4 + min: 4, }, moneyAvailable: { max: 900000000, @@ -533,7 +533,7 @@ export const serverMetadata: IServerMetadata[] = [ literature: ["synthetic-muscles.lit"], maxRamExponent: { max: 6, - min: 4 + min: 4, }, moneyAvailable: { max: 700000000, @@ -631,7 +631,7 @@ export const serverMetadata: IServerMetadata[] = [ literature: ["history-of-synthoids.lit"], maxRamExponent: { max: 6, - min: 4 + min: 4, }, moneyAvailable: { max: 1000000000, @@ -706,7 +706,7 @@ export const serverMetadata: IServerMetadata[] = [ ], maxRamExponent: { max: 7, - min: 4 + min: 4, }, moneyAvailable: { max: 900000000, @@ -755,7 +755,7 @@ export const serverMetadata: IServerMetadata[] = [ literature: ["A-Green-Tomorrow.lit"], maxRamExponent: { max: 6, - min: 3 + min: 3, }, moneyAvailable: { max: 1750000000, @@ -825,7 +825,7 @@ export const serverMetadata: IServerMetadata[] = [ hostname: "unitalife", maxRamExponent: { max: 6, - min: 4 + min: 4, }, moneyAvailable: { max: 1100000000, @@ -851,7 +851,7 @@ export const serverMetadata: IServerMetadata[] = [ hostname: "lexo-corp", maxRamExponent: { max: 7, - min: 4 + min: 4, }, moneyAvailable: { max: 800000000, @@ -877,7 +877,7 @@ export const serverMetadata: IServerMetadata[] = [ hostname: "rho-construction", maxRamExponent: { max: 6, - min: 4 + min: 4, }, moneyAvailable: { max: 700000000, @@ -904,7 +904,7 @@ export const serverMetadata: IServerMetadata[] = [ literature: ["sector-12-crime.lit"], maxRamExponent: { max: 7, - min: 4 + min: 4, }, moneyAvailable: { max: 750000000, @@ -930,7 +930,7 @@ export const serverMetadata: IServerMetadata[] = [ hostname: "aevum-police", maxRamExponent: { max: 6, - min: 4 + min: 4, }, moneyAvailable: { max: 400000000, @@ -961,7 +961,7 @@ export const serverMetadata: IServerMetadata[] = [ ], maxRamExponent: { max: 7, - min: 4 + min: 4, }, moneyAvailable: { max: 250000000, @@ -987,7 +987,7 @@ export const serverMetadata: IServerMetadata[] = [ hostname: "zb-institute", maxRamExponent: { max: 7, - min: 4 + min: 4, }, moneyAvailable: { max: 1100000000, @@ -1018,7 +1018,7 @@ export const serverMetadata: IServerMetadata[] = [ ], maxRamExponent: { max: 6, - min: 4 + min: 4, }, moneyAvailable: { max: 350000000, @@ -1067,7 +1067,7 @@ export const serverMetadata: IServerMetadata[] = [ literature: ["tensions-in-tech-race.lit"], maxRamExponent: { max: 7, - min: 4 + min: 4, }, moneyAvailable: { max: 550000000, @@ -1093,7 +1093,7 @@ export const serverMetadata: IServerMetadata[] = [ hostname: "the-hub", maxRamExponent: { max: 6, - min: 3 + min: 3, }, moneyAvailable: { max: 200000000, @@ -1143,7 +1143,7 @@ export const serverMetadata: IServerMetadata[] = [ literature: ["simulated-reality.lit"], maxRamExponent: { max: 7, - min: 4 + min: 4, }, moneyAvailable: 275000000, networkLayer: 4, @@ -1370,7 +1370,7 @@ export const serverMetadata: IServerMetadata[] = [ hostname: "millenium-fitness", maxRamExponent: { max: 8, - min: 4 + min: 4, }, moneyAvailable: 250000000, networkLayer: 6, @@ -1393,7 +1393,7 @@ export const serverMetadata: IServerMetadata[] = [ hostname: "powerhouse-fitness", maxRamExponent: { max: 6, - min: 4 + min: 4, }, moneyAvailable: 900000000, networkLayer: 14, @@ -1436,7 +1436,7 @@ export const serverMetadata: IServerMetadata[] = [ ], maxRamExponent: { max: 9, - min: 5 + min: 5, }, moneyAvailable: 0, networkLayer: 11, @@ -1455,7 +1455,7 @@ export const serverMetadata: IServerMetadata[] = [ literature: ["democracy-is-dead.lit"], maxRamExponent: { max: 8, - min: 4 + min: 4, }, moneyAvailable: 0, networkLayer: 5, @@ -1474,7 +1474,7 @@ export const serverMetadata: IServerMetadata[] = [ literature: ["democracy-is-dead.lit"], maxRamExponent: { max: 7, - min: 4 + min: 4, }, moneyAvailable: 0, networkLayer: 4, diff --git a/tsconfig.json b/tsconfig.json index b5ff056a6..7f31edd5f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "lib" : ["es2016", "dom"], "module": "commonjs", "target": "es6", "sourceMap": true, diff --git a/utils/InfiltrationBox.js b/utils/InfiltrationBox.js index 8c1290179..18667bd82 100644 --- a/utils/InfiltrationBox.js +++ b/utils/InfiltrationBox.js @@ -17,7 +17,7 @@ function infiltrationBoxClose() { function infiltrationBoxOpen() { var box = document.getElementById("infiltration-box-container"); - box.style.display = "block"; + box.style.display = "flex"; } function infiltrationSetText(txt) {