From f78f0ec1a7e5c19dfaf978ce9c8a58463a5ea46a Mon Sep 17 00:00:00 2001 From: danielyxie Date: Sat, 22 Sep 2018 19:25:48 -0500 Subject: [PATCH] Implemented Coding Contracts --- doc/source/codingcontracts.rst | 47 ++++++++++ doc/source/index.rst | 1 + doc/source/netscript.rst | 1 + doc/source/netscriptbladeburnerapi.rst | 7 +- doc/source/netscriptcodingcontractapi.rst | 74 +++++++++++++++ doc/source/netscriptfunctions.rst | 2 +- doc/source/terminal.rst | 15 +++- netscript.js | 13 +-- src/CodingContracts.ts | 25 ++++-- src/Constants.js | 39 ++------ src/NetscriptFunctions.js | 104 +++++++++++++++++++++- src/Player.js | 20 ++++- src/Script.js | 4 +- src/Terminal.js | 36 ++++++-- src/data/codingcontracttypes.ts | 40 +++++++-- src/engine.js | 61 +++++++++++-- utils/uiHelpers/createElement.ts | 4 + 17 files changed, 413 insertions(+), 80 deletions(-) create mode 100644 doc/source/codingcontracts.rst create mode 100644 doc/source/netscriptcodingcontractapi.rst diff --git a/doc/source/codingcontracts.rst b/doc/source/codingcontracts.rst new file mode 100644 index 000000000..a27859904 --- /dev/null +++ b/doc/source/codingcontracts.rst @@ -0,0 +1,47 @@ +.. _codingcontracts: + +Coding Contracts +================ +Coding Contracts are a mechanic that lets players earn rewards in +exchange for solving programming problems. + +Coding Contracts are files with the ".cct" extensions. They can +be accessed through the :ref:`terminal` or through scripts using +the :ref:`netscriptcodingcontractapi` + +Each contract has a limited number of attempts. If you +provide the wrong answer too many times and exceed the +number of attempts, the contract will self destruct (delete itself) + +Currently, Coding Contracts are randomly generated and +spawned over time. They can appear on any server (including your +home computer), except for your purchased servers. + + +Running in Terminal +^^^^^^^^^^^^^^^^^^^ +To run a Coding Contract in the Terminal, simply use the +:ref:`run_terminal_command` command:: + + $ run some-contract.cct + +Doing this will bring up a popup. The popup will display +the contract's problem, the number of attempts remaining, and +an area to provide an answer. + +Interacting through Scripts +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +See :ref:`netscriptcodingcontractapi`. + +Rewards +^^^^^^^ +There are currently four possible rewards for solving a Coding Contract: + +* Faction Reputation for a specific Faction +* Faction Reputation for all Factions that you are a member of +* Company reputation for a specific Company +* Money + +The 'amount' of reward varies based on the difficulty of the problem +posed by the Coding Contract. There is no way to know what a +Coding Contract's exact reward will be until it is solved. diff --git a/doc/source/index.rst b/doc/source/index.rst index f71bf236c..bdd008747 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -21,6 +21,7 @@ secrets that you've been searching for. Netscript Terminal + Coding Contracts Keyboard Shortcuts Changelog diff --git a/doc/source/netscript.rst b/doc/source/netscript.rst index 13272cd50..3f41c710c 100644 --- a/doc/source/netscript.rst +++ b/doc/source/netscript.rst @@ -25,4 +25,5 @@ to reach out to the developer! Trade Information eXchange (TIX) API Singularity Functions Bladeburner API + Coding Contract API Miscellaneous diff --git a/doc/source/netscriptbladeburnerapi.rst b/doc/source/netscriptbladeburnerapi.rst index 1c211c01a..c874ddea5 100644 --- a/doc/source/netscriptbladeburnerapi.rst +++ b/doc/source/netscriptbladeburnerapi.rst @@ -1,9 +1,8 @@ Netscript Bladeburner API ========================= - Netscript provides the following API for interacting with the game's Bladeburner mechanic. -The Bladeburner API is **not** immediately available to the palyer and must be unlocked +The Bladeburner API is **not** immediately available to the player and must be unlocked later in the game **WARNING: This page contains spoilers for the game** @@ -12,9 +11,9 @@ The Bladeburner API is unlocked in BitNode-7. If you are in BitNode-7, you will automatically gain access to this API. Otherwise, you must have Source-File 7 in order to use this API in other BitNodes -**Bladeburner API functions must be accessed through the bladeburner namespace** +**Bladeburner API functions must be accessed through the 'bladeburner' namespace** -In Netscript 1.0:: +In :ref:`netscript1`:: bladeburner.getContractNames(); bladeburner.startAction("general", "Training"); diff --git a/doc/source/netscriptcodingcontractapi.rst b/doc/source/netscriptcodingcontractapi.rst new file mode 100644 index 000000000..b5dd0714f --- /dev/null +++ b/doc/source/netscriptcodingcontractapi.rst @@ -0,0 +1,74 @@ +.. _netscriptcodingcontractapi: + +Netscript Coding Contract API +============================= +Netscript provides the following API for interacting with +:ref:`codingcontracts`. + +**The Coding Contract API must be accessed through the 'codingcontract' namespace** + +In :ref:`netscript1`:: + + codingcontract.getDescription("foo.cct", "home"); + codingcontract.attempt(1, "foo.cct", "foodnstuff"); + +In :ref:`netscriptjs`:: + + ns.codingcontract.getDescription("foo.cct", "home"); + ns.codingcontract.attempt(1, "foo.cct", "foodnstuff"); + +attempt +------- + +.. js:function:: attempt(answer, fn[, hostname/ip=current ip]) + + :param answer: Solution for the contract + :param string fn: Filename of the contract + :param string hostname/ip: Hostname or IP of the server containing the contract. + Optional. Defaults to current server if not provided + + Attempts to solve the Coding Contract with the provided solution. + + :returns: Boolean indicating whether the solution was correct + +getDescription +-------------- + +.. js:function:: getDescription(fn[, hostname/ip=current ip]) + + :param string fn: Filename of the contract + :param string hostname/ip: Hostname or IP of the server containing the contract. + Optional. Defaults to current server if not provided + + Get the full text description for the problem posed by the Coding Contract + + :returns: A string with the contract's text description + +getData +------- + +.. js:function:: getData(fn[, hostname/ip=current ip]) + + :param string fn: Filename of the contract + :param string hostname/ip: Hostname or IP of the server containing the contract. + Optional. Defaults to current server if not provided + + Get the data associated with the specific Coding Contract. Note that this is + not the same as the contract's description. This is just the data that + the contract wants you to act on in order to solve + + :returns: The specified contract's data + +getNumTriesRemaining +-------------------- + +.. js:function:: getNumTriesRemaining(fn[, hostname/ip=current ip]) + + :param string fn: Filename of the contract + :param string hostname/ip: Hostname or IP of the server containing the contract. + Optional. Defaults to current server if not provided + + Get the number of tries remaining on the contract before it + self-destructs. + + :returns: Number indicating how many attempts are remaining diff --git a/doc/source/netscriptfunctions.rst b/doc/source/netscriptfunctions.rst index 124fd3845..0c1ae5e45 100644 --- a/doc/source/netscriptfunctions.rst +++ b/doc/source/netscriptfunctions.rst @@ -154,7 +154,7 @@ getScriptLogs scan ^^^^ -.. js:function:: scan(hostname/ip[, hostnames=true]) +.. js:function:: scan(hostname/ip=current ip[, hostnames=true]) :param string hostname/ip: IP or hostname of the server to scan :param boolean: Optional boolean specifying whether the function should output hostnames (if true) or IP addresses (if false) diff --git a/doc/source/terminal.rst b/doc/source/terminal.rst index 257f7542f..51f9e25ee 100644 --- a/doc/source/terminal.rst +++ b/doc/source/terminal.rst @@ -285,12 +285,15 @@ except literature files (.lit). **WARNING: This is permanent and cannot be undone** + +.. _run_terminal_command: + run ^^^ $ run [file name] [-t] [num threads] [args...] -Execute a program or a script. +Execute a program, script, or :ref:`codingcontracts`. The '[-t]', '[num threads]', and '[args...]' arguments are only valid when running a script. The '-t' flag is used to indicate that the script should @@ -305,13 +308,17 @@ argument must be separated by a space. **Examples** -Run a program: +Run a program:: - run BruteSSH.exe + $ run BruteSSH.exe Run *foo.script* with 50 threads and the arguments [1e3, 0.5, foodnstuff]:: - run foo.script -t 50 1e3 0.5 foodnstuff + $ run foo.script -t 50 1e3 0.5 foodnstuff + +Run a Coding Contract:: + + $ run foo-contract.cct scan ^^^^ diff --git a/netscript.js b/netscript.js index d61382a0c..5886155ae 100644 --- a/netscript.js +++ b/netscript.js @@ -78,7 +78,7 @@ let NetscriptFunctions = "getHackTime|getGrowTime|getWeakenTime|getScriptIncome|getScriptExpGain|" + "getTimeSinceLastAug|prompt|" + - //Singularity Functions + // Singularity Functions "universityCourse|getCharacterInformation|" + "gymWorkout|travelToCity|purchaseTor|purchaseProgram|upgradeHomeRam|" + "getUpgradeHomeRamCost|workForCompany|applyToCompany|getCompanyRep|" + @@ -91,16 +91,16 @@ let NetscriptFunctions = "getAugmentationCost|purchaseAugmentation|" + "installAugmentations|" + - //TIX API + // TIX API "getStockPrice|getStockPosition|buyStock|sellStock|shortStock|sellShort|" + "placeOrder|cancelOrder|" + - //Hacknet Node API + // Hacknet Node API "hacknet|numNodes|purchaseNode|getPurchaseNodeCost|getNodeStats|" + "upgradeLevel|upgradeRam|upgradeCore|getLevelUpgradeCost|" + "getRamUpgradeCost|getCoreUpgradeCost|" + - //Bladeburner functions + // Bladeburner functions "bladeburner|getContractNames|getOperationNames|getBlackOpNames|" + "getGeneralActionNames|getSkillNames|startAction|stopBladeburnerAction|" + "getActionTime|getActionEstimatedSuccessChance|getActionCountRemaining|" + @@ -109,7 +109,10 @@ let NetscriptFunctions = "getRank|getSkillPoints|getSkillLevel|getSkillUpgradeCost|" + "upgradeSkill|getTeamSize|getCity|" + "setTeamSize|getCityEstimatedPopulation|getCityEstimatedCommunities|" + - "getCityChaos|switchCity|getStamina|joinBladeburnerFaction|getBonusTime"; + "getCityChaos|switchCity|getStamina|joinBladeburnerFaction|getBonusTime|" + + + // Coding Contract API + "codingcontract|attempt|getData|getDescription|getNumTriesRemaining"; var NetscriptHighlightRules = function(options) { var keywordMapper = this.createKeywordMapper({ diff --git a/src/CodingContracts.ts b/src/CodingContracts.ts index 6b6550de0..ace0bc44c 100644 --- a/src/CodingContracts.ts +++ b/src/CodingContracts.ts @@ -70,7 +70,7 @@ export enum CodingContractRewardType { FactionReputation, FactionReputationAll, CompanyReputation, - Money, // This must always be the last reward type + Money, // This must always be the last reward type } /** @@ -137,6 +137,14 @@ export class CodingContract { this.reward = reward; } + getData(): any { + return this.data; + } + + getDescription(): string { + return CodingContractTypes[this.type].desc(this.data); + } + getDifficulty(): number { return CodingContractTypes[this.type].difficulty; } @@ -155,15 +163,21 @@ export class CodingContract { async prompt(): Promise { // tslint:disable-next-line return new Promise((resolve: Function, reject: Function) => { - const contractType: ContractType = CodingContractTypes[this.type]; + const contractType: CodingContractType = CodingContractTypes[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", + innerText: ["You are attempting to solve a Coding Contract. You have", + `${this.getMaxNumTries() - this.tries} tries remaining,`, + "after which the contract will self-destruct.\n\n", `${contractType.desc(this.data)}`].join(" "), }); const answerInput: HTMLInputElement = createElement("input", { + onkeydown:(e)=>{ + if (e.keyCode === 13 && answerInput.value !== "") { + e.preventDefault(); + solveBtn.click(); + } + }, placeholder: "Enter Solution here", }) as HTMLInputElement; const solveBtn: HTMLElement = createElement("a", { @@ -189,6 +203,7 @@ export class CodingContract { }); const lineBreak: HTMLElement = createElement("br"); createPopup(popupId, [txt, lineBreak, lineBreak, answerInput, solveBtn, cancelBtn]); + answerInput.focus(); }); } diff --git a/src/Constants.js b/src/Constants.js index 1c04f5217..a6b856d20 100644 --- a/src/Constants.js +++ b/src/Constants.js @@ -85,8 +85,7 @@ let CONSTANTS = { ScriptGetScriptRamCost: 0.1, ScriptGetHackTimeRamCost: 0.05, ScriptGetFavorToDonate: 0.10, - ScriptGetContractDataRamCost: 25, - ScriptAttemptContractRamCost: 25, + ScriptCodingContractBaseRamCost:10, ScriptSingularityFn1RamCost: 1, ScriptSingularityFn2RamCost: 2, @@ -274,7 +273,7 @@ let CONSTANTS = { /* Coding Contract Constants */ CodingContractBaseFactionRepGain: 2500, CodingContractBaseCompanyRepGain: 4000, - CodingContractBaseMoneyGain: 10e6, + CodingContractBaseMoneyGain: 50e6, /* Tutorial related things */ TutorialNetworkingText: "Servers are a central part of the game. You start with a single personal server (your home computer) " + @@ -508,35 +507,13 @@ let CONSTANTS = { LatestUpdate: ` 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)
+ * Added new Coding Contracts mechanic. Solve programming problems to earn rewards + * (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() + * The game's theme colors can now be set through the Terminal configuration (.fconf). + * You can now switch to the old left-hand main menu bar through the Terminal configuration (.fconf) + ` - v0.40.3
- -----------------------------------------------
- * Bladeburner Changes:
- *** Increased the effect that agi and dexterity have on action time
- *** Starting number of contracts/operations available will be slightly lower
- *** Random events will now happen slightly more often
- *** Slightly increased the rate at which the Overclock skill point cost increases
- -----------------------------------------------
- * The maximum volatility of stocks is now randomized (randomly generated within a certain range every time the game resets)
- * Increased the range of possible values for initial stock prices
- * b1t_flum3.exe program can now be created immediately at Hacking level 1 (rather than hacking level 5)
- * UI improvements for the character overview panel and the left-hand menu (by mat-jaworski)
- * General UI improvements for displays and Terminal (by mat-jaworski)
- * Added optional parameters to the getHackTime(), getGrowTime(), and getWeakenTime() Netscript functions
- * Added isLogEnabled() and getScriptLogs() Netscript functions
- * Added donateToFaction() Singularity function
- * Updated documentation to reflect the fact that Netscript port handles (getPortHandle()) only works in NetscriptJS (2.0), NOT Netscript 1.0
- * Added tryWrite() Netscript function
- * When working (for a company/faction), experience is gained immediately/continuously rather than all at once when the work is finished
- * Added a setting in .fconf for enabling line-wrap in the Terminal input
- * Adding a game option for changing the locale that most numbers are displayed in (this mostly applies for whenever money is displayed)
- * The randomized parameters of many high-level servers can now take on a higher range of values
- * Many 'foreign' servers (hackable servers that you don't own) now have a randomized amount of RAM
- * Added 'wget' Terminal command
- * Improved the introductory tutorial` } export {CONSTANTS}; diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js index 19c79d8c3..896c4faf2 100644 --- a/src/NetscriptFunctions.js +++ b/src/NetscriptFunctions.js @@ -175,7 +175,21 @@ function NetscriptFunctions(workerScript) { } }; - //Utility function to get Hacknet Node object + /** + * Gets the Server for a specific hostname/ip, throwing an error + * if the server doesn't exist. + * @param {string} Hostname or IP of the server + * @returns {Server} The specified Server + */ + var safeGetServer = function(ip, callingFnName="") { + var server = getServer(ip); + if (server == null) { + throw makeRuntimeRejectMsg(workerScript, `Invalid IP or hostname passed into ${callingFnName}() function`); + } + return server; + } + + // Utility function to get Hacknet Node object var getHacknetNode = function(i) { if (isNaN(i)) { throw makeRuntimeRejectMsg(workerScript, "Invalid index specified for Hacknet Node: " + i); @@ -186,6 +200,11 @@ function NetscriptFunctions(workerScript) { return Player.hacknetNodes[i]; }; + var getCodingContract = function(fn, ip) { + var server = safeGetServer(ip, "getCodingContract"); + return server.getContract(fn); + } + /** * @param {number} ram The amount of server RAM to calculate cost of. * @exception {Error} If the value passed in is not numeric, out of range, or too large of a value. @@ -1057,6 +1076,16 @@ function NetscriptFunctions(workerScript) { } } + for (var i = 0; i < server.contracts.length; ++i) { + if (filter) { + if (server.contracts[i].fn.includes(filter)) { + allFiles.push(server.contracts[i].fn); + } + } else { + allFiles.push(server.contracts[i].fn); + } + } + //Sort the files alphabetically then print each allFiles.sort(); return allFiles; @@ -2040,6 +2069,13 @@ function NetscriptFunctions(workerScript) { return true; } } + } else if (fn.endsWith(".cct")) { + for (var i = 0; i < s.contracts.length; ++i) { + if (s.contracts[i].fn === fn) { + s.contracts.splice(i, 1); + return true; + } + } } return false; }, @@ -3947,6 +3983,72 @@ function NetscriptFunctions(workerScript) { throw makeRuntimeRejectMsg(workerScript, "getBonusTime() failed because you do not currently have access to the Bladeburner API. This is either because you are not currently employed " + "at the Bladeburner division or because you do not have Source-File 7"); } + }, // End Bladeburner + codingcontract : { + attempt : function(answer, fn, ip=workerScript.serverIp) { + if (workerScript.checkingRam) { + return updateStaticRam("attempt", CONSTANTS.ScriptCodingContractBaseRamCost); + } + updateDynamicRam("attempt", CONSTANTS.ScriptCodingContractBaseRamCost); + const contract = getCodingContract(fn, ip); + if (contract == null) { + workerScript.log(`ERROR: codingcontract.getData() failed because it could find the specified contract ${fn} on server ${ip}`); + return false; + } + answer = String(answer); + const serv = safeGetServer(ip, "codingcontract.attempt()"); + if (contract.isSolution(answer)) { + const reward = Player.gainCodingContractReward(contract.reward, contract.getDifficulty()); + workerScript.log(`Successfully completed Coding Contract ${fn}. Reward: ${reward}`); + serv.removeContract(fn); + return true; + } else { + ++contract.tries; + if (contract.tries >= contract.getMaxNumTries()) { + workerScript.log(`Coding Contract ${fn} failed. Contract is now self-destructing`); + serv.removeContract(fn); + } else { + workerScript.log(`Coding Contract ${fn} failed. ${contract.getMaxNumTries() - contract.tries} attempts remaining`); + } + return false; + } + }, + getData : function(fn, ip=workerScript.serverIp) { + if (workerScript.checkingRam) { + return updateStaticRam("getData", CONSTANTS.ScriptCodingContractBaseRamCost / 2); + } + updateDynamicRam("getData", CONSTANTS.ScriptCodingContractBaseRamCost / 2); + var contract = getCodingContract(fn, ip); + if (contract == null) { + workerScript.log(`ERROR: codingcontract.getData() failed because it could find the specified contract ${fn} on server ${ip}`); + return null; + } + return contract.getData(); + }, + getDescription : function(fn, ip=workerScript.serverIp) { + if (workerScript.checkingRam) { + return updateStaticRam("getDescription", CONSTANTS.ScriptCodingContractBaseRamCost / 2); + } + updateDynamicRam("getDescription", CONSTANTS.ScriptCodingContractBaseRamCost / 2); + var contract = getCodingContract(fn, ip); + if (contract == null) { + workerScript.log(`ERROR: codingcontract.getDescription() failed because it could find the specified contract ${fn} on server ${ip}`); + return ""; + } + return contract.getDescription(); + }, + getNumTriesRemaining : function(fn, ip=workerScript.serverIp) { + if (workerScript.checkingRam) { + return updateStaticRam("getNumTriesRemaining", CONSTANTS.ScriptCodingContractBaseRamCost / 2); + } + updateDynamicRam("getNumTriesRemaining", CONSTANTS.ScriptCodingContractBaseRamCost / 2); + var contract = getCodingContract(fn, ip); + if (contract == null) { + workerScript.log(`ERROR: codingcontract.getNumTriesRemaining() failed because it could find the specified contract ${fn} on server ${ip}`); + return -1; + } + return contract.getMaxNumTries() - contract.tries; + }, } } //End return } //End NetscriptFunction() diff --git a/src/Player.js b/src/Player.js index 39516f995..de2ae10c2 100644 --- a/src/Player.js +++ b/src/Player.js @@ -2298,12 +2298,26 @@ PlayerObject.prototype.gainCodingContractReward = function(reward, difficulty=1) 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) { + + // Ignore Bladeburners and other special factions for this calculation + const specialFactions = ["Bladeburners"]; + var factions = this.factions.slice(); + factions = factions.filter((f) => { + return !specialFactions.includes(f); + }); + + // If the player was only part of the special factions, we'll just give money + if (factions.length == 0) { + reward.type = CodingContractRewardType.Money; + return this.gainCodingContractReward(reward, difficulty); + } + + const gainPerFaction = Math.floor(totalGain / factions.length); + for (const facName of factions) { if (!(Factions[facName] instanceof Faction)) { continue; } Factions[facName].playerReputation += gainPerFaction; } - return `Gained ${gainPerFaction} reputation for each faction you are a member of`; + return `Gained ${gainPerFaction} reputation for each of the following factions: ${factions.toString()}`; break; case CodingContractRewardType.CompanyReputation: if (reward.name == null || !(Companies[reward.name] instanceof Company)) { diff --git a/src/Script.js b/src/Script.js index 9cfbb58f4..241433162 100755 --- a/src/Script.js +++ b/src/Script.js @@ -531,10 +531,12 @@ function parseOnlyRamCalculate(server, code, workerScript) { } } - //Special logic for Bladeburner + //Special logic for namespaces (Bladeburner, CodingCOntract) var func; if (ref in workerScript.env.vars.bladeburner) { func = workerScript.env.vars.bladeburner[ref]; + } else if (ref in workerScript.env.vars.codingcontract) { + func = workerScript.env.vars.codingcontract[ref]; } else { func = workerScript.env.get(ref); } diff --git a/src/Terminal.js b/src/Terminal.js index 7813c4d71..37bb0eb66 100644 --- a/src/Terminal.js +++ b/src/Terminal.js @@ -458,35 +458,42 @@ function determineAllPossibilitiesForTabCompletion(input, index=0) { } if (input.startsWith("rm ")) { - for (var i = 0; i < currServ.scripts.length; ++i) { + for (let i = 0; i < currServ.scripts.length; ++i) { allPos.push(currServ.scripts[i].filename); } - for (var i = 0; i < currServ.programs.length; ++i) { + for (let i = 0; i < currServ.programs.length; ++i) { allPos.push(currServ.programs[i]); } - for (var i = 0; i < currServ.messages.length; ++i) { + for (let i = 0; i < currServ.messages.length; ++i) { if (!(currServ.messages[i] instanceof Message) && isString(currServ.messages[i]) && currServ.messages[i].endsWith(".lit")) { allPos.push(currServ.messages[i]); } } - for (var i = 0; i < currServ.textFiles.length; ++i) { + for (let i = 0; i < currServ.textFiles.length; ++i) { allPos.push(currServ.textFiles[i].fn); } + for (let i = 0; i < currServ.contracts.length; ++i) { + allPos.push(currServ.contracts[i].fn); + } return allPos; } if (input.startsWith("run ")) { - //All programs and scripts - for (var i = 0; i < currServ.scripts.length; ++i) { + //All programs, scripts, and contracts + for (let i = 0; i < currServ.scripts.length; ++i) { allPos.push(currServ.scripts[i].filename); } //Programs are on home computer var homeComputer = Player.getHomeComputer(); - for(var i = 0; i < homeComputer.programs.length; ++i) { + for (let i = 0; i < homeComputer.programs.length; ++i) { allPos.push(homeComputer.programs[i]); } + + for (let i = 0; i < currServ.contracts.length; ++i) { + allPos.push(currServ.contracts[i].fn); + } return allPos; } @@ -1341,6 +1348,13 @@ let Terminal = { return; } } + } else if (delTarget.endsWith(".cct")) { + for (var i = 0; i < s.contracts.length; ++i) { + if (s.contracts[i].fn === delTarget) { + s.contracts.splice(i, 1); + return; + } + } } post("Error: No such file exists"); break; @@ -2154,13 +2168,14 @@ let Terminal = { 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"); } + + Terminal.contractOpen = true; const res = await contract.prompt(); switch (res) { @@ -2170,10 +2185,12 @@ let Terminal = { serv.removeContract(contract); break; case CodingContractResult.Failure: - post("Contract

FAILED

- Contract is now self-destructing"); ++contract.tries; if (contract.tries >= contract.getMaxNumTries()) { + post("Contract

FAILED

- Contract is now self-destructing"); serv.removeContract(contract); + } else { + post(`Contract

FAILED

- ${contract.getMaxNumTries() - contract.tries} tries remaining`); } break; case CodingContractResult.Cancelled: @@ -2182,6 +2199,7 @@ let Terminal = { break; } Terminal.contractOpen = false; + console.log(Terminal.contractOpen); }, }; diff --git a/src/data/codingcontracttypes.ts b/src/data/codingcontracttypes.ts index 786e6ca6f..67608c8cc 100644 --- a/src/data/codingcontracttypes.ts +++ b/src/data/codingcontracttypes.ts @@ -21,6 +21,28 @@ export interface ICodingContractTypeMetadata { solver: SolverFunc; } +/* Helper functions for Coding Contract implementations */ +function removeBracketsFromArrayString(str: string) { + let strCpy: string = str; + if (strCpy.startsWith("[")) { strCpy = strCpy.slice(1); } + if (strCpy.endsWith("]")) { strCpy = strCpy.slice(-1); } + + return strCpy; +} + +function convert2DArrayToString(arr: any[][]) { + let res = ""; + const components: string[] = []; + arr.forEach((e) => { + let s = e.toString(); + s = ["[", s, "]"].join(""); + components.push(s); + }); + + res = components.join(","); + return res.replace(/\s/g, ""); +} + export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [ { desc: (n: number) => { @@ -136,6 +158,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [ const matrix: number[][] = []; matrix.length = m; for (let i: number = 0; i < m; ++i) { + matrix[i] = []; matrix[i].length = n; } @@ -187,7 +210,9 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [ } if (++l > r) { break; } } - const playerAns: any[] = ans.split(","); + + const sanitizedPlayerAns: string = removeBracketsFromArrayString(ans); + const playerAns: any[] = sanitizedPlayerAns.split(","); for (let i: number = 0; i < playerAns.length; ++i) { playerAns[i] = parseInt(playerAns[i], 10); } @@ -282,22 +307,21 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [ } result.push([start, end]); - const sanitizedResult: string = result - .toString() - .replace(/\s/g, ""); + const sanitizedResult: string = convert2DArrayToString(result); const sanitizedAns: string = ans.replace(/\s/g, ""); - return sanitizedResult === sanitizedAns; + return (sanitizedResult === sanitizedAns || + sanitizedResult === removeBracketsFromArrayString(sanitizedAns)); }, }, { desc: (data: string) => { - return ["Given the following string containing only digits, determine", + return ["Given the following string containing only digits, return", "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(" "); + "25525511135 -> [255.255.11.135, 255.255.111.35]"].join(" "); }, difficulty: 3, gen: () => { @@ -425,7 +449,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [ 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 ", + "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", diff --git a/src/engine.js b/src/engine.js index 96ebf636b..db4e68c30 100644 --- a/src/engine.js +++ b/src/engine.js @@ -917,7 +917,6 @@ const Engine = { }, updateGame: function(numCycles = 1) { - //Update total playtime var time = numCycles * Engine._idleSpeed; if (Player.totalPlaytime == null) {Player.totalPlaytime = 0;} if (Player.playtimeSinceLastAug == null) {Player.playtimeSinceLastAug = 0;} @@ -1155,20 +1154,66 @@ const Engine = { } if (Engine.Counters.contractGeneration <= 0) { - // 5% chance of a contract being generated - if (Math.random() <= 0.05) { + // 20% chance of a contract being generated + if (Math.random() < 0.2) { // First select a random problem type - let problemTypes = Object.keys(CodingContractTypes); + const problemTypes = Object.keys(CodingContractTypes); let randIndex = getRandomInt(0, problemTypes.length - 1); - let problemType = CodingContractTypes[problemTypes[randIndex]]; + let problemType = problemTypes[randIndex]; // Then select a random reward type. 'Money' will always be the last reward type - let rewardType = getRandomInt(1, CodingContractRewardType.Money); + var reward = {}; + reward.type = getRandomInt(0, CodingContractRewardType.Money); + + // Change type based on certain conditions + if (Player.factions.length === 0) { reward.type = CodingContractRewardType.CompanyReputation; } + if (Player.companyName === "") { reward.type = CodingContractRewardType.Money; } // Add additional information based on the reward type - switch (rewardType) { - case CodingContractRewardType + switch (reward.type) { + case CodingContractRewardType.FactionReputation: + // Get a random faction that player is a part of. That + //faction must allow hacking contracts + var numFactions = Player.factions.length; + var randFaction = Player.factions[getRandomInt(0, numFactions - 1)]; + try { + while(Factions[randFaction].getInfo().offerHackingWork !== true) { + randFaction = Player.factions[getRandomInt(0, numFactions - 1)]; + } + reward.name = randFaction; + } catch (e) { + exceptionAlert("Failed to find a faction for Coding Contract Generation: " + e); + } + + break; + case CodingContractRewardType.CompanyReputation: + if (Player.companyName !== "") { + reward.name = Player.companyName; + } else { + reward.type = CodingContractRewardType.Money; + } + break; + default: + break; } + + // Choose random server + const servers = Object.keys(AllServers); + randIndex = getRandomInt(0, servers.length - 1); + var randServer = AllServers[servers[randIndex]]; + while (randServer.purchasedByPlayer === true) { + randIndex = getRandomInt(0, servers.length - 1); + randServer = AllServers[servers[randIndex]]; + } + + let contractFn = `contract-${getRandomInt(0, 1e6)}`; + while (randServer.contracts.filter((c) => {return c.fn === contractFn}).length > 0) { + contractFn = `contract-${getRandomInt(0, 1e6)}`; + } + if (reward.name) { contractFn += `-${reward.name.replace(/\s/g, "")}`; } + let contract = new CodingContract(contractFn, problemType, reward); + + randServer.addContract(contract); } Engine.Counters.contractGeneration = 3000; } diff --git a/utils/uiHelpers/createElement.ts b/utils/uiHelpers/createElement.ts index dae1b4a4c..3be5b2590 100644 --- a/utils/uiHelpers/createElement.ts +++ b/utils/uiHelpers/createElement.ts @@ -35,6 +35,7 @@ interface ICreateElementListenerOptions { clickListener?(this: HTMLElement, ev: MouseEvent): any; inputListener?(this: HTMLElement, ev: Event): any; onfocus?(this: HTMLElement, ev: FocusEvent): any; + onkeydown?(this: HTMLElement, ev: KeyboardEvent): any; onkeyup?(this: HTMLElement, ev: KeyboardEvent): any; } @@ -148,6 +149,9 @@ function setElementListeners(el: HTMLElement, params: ICreateElementListenerOpti if (params.onkeyup !== undefined) { el.addEventListener("keyup", params.onkeyup); } + if (params.onkeydown !== undefined) { + el.addEventListener("keydown", params.onkeydown); + } if (params.onfocus !== undefined) { el.addEventListener("focus", params.onfocus); }