Implemented Coding Contracts

This commit is contained in:
danielyxie
2018-09-22 19:25:48 -05:00
parent e714f1e6cd
commit f78f0ec1a7
17 changed files with 413 additions and 80 deletions
+20 -5
View File
@@ -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<CodingContractResult> {
// tslint:disable-next-line
return new Promise<CodingContractResult>((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();
});
}
+8 -31
View File
@@ -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<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>
* 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<br>
-----------------------------------------------<br>
* Bladeburner Changes:<br>
*** Increased the effect that agi and dexterity have on action time<br>
*** Starting number of contracts/operations available will be slightly lower<br>
*** Random events will now happen slightly more often<br>
*** Slightly increased the rate at which the Overclock skill point cost increases<br>
-----------------------------------------------<br>
* The maximum volatility of stocks is now randomized (randomly generated within a certain range every time the game resets)<br>
* Increased the range of possible values for initial stock prices<br>
* b1t_flum3.exe program can now be created immediately at Hacking level 1 (rather than hacking level 5)<br>
* UI improvements for the character overview panel and the left-hand menu (by mat-jaworski)<br>
* General UI improvements for displays and Terminal (by mat-jaworski)<br>
* Added optional parameters to the getHackTime(), getGrowTime(), and getWeakenTime() Netscript functions<br>
* Added isLogEnabled() and getScriptLogs() Netscript functions<br>
* Added donateToFaction() Singularity function<br>
* Updated documentation to reflect the fact that Netscript port handles (getPortHandle()) only works in NetscriptJS (2.0), NOT Netscript 1.0<br>
* Added tryWrite() Netscript function<br>
* When working (for a company/faction), experience is gained immediately/continuously rather than all at once when the work is finished<br>
* Added a setting in .fconf for enabling line-wrap in the Terminal input<br>
* Adding a game option for changing the locale that most numbers are displayed in (this mostly applies for whenever money is displayed)<br>
* The randomized parameters of many high-level servers can now take on a higher range of values<br>
* Many 'foreign' servers (hackable servers that you don't own) now have a randomized amount of RAM<br>
* Added 'wget' Terminal command<br>
* Improved the introductory tutorial`
}
export {CONSTANTS};
+103 -1
View File
@@ -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()
+17 -3
View File
@@ -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)) {
+3 -1
View File
@@ -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);
}
+27 -9
View File
@@ -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 <p style='color:red;display:inline'>FAILED</p> - Contract is now self-destructing");
++contract.tries;
if (contract.tries >= contract.getMaxNumTries()) {
post("Contract <p style='color:red;display:inline'>FAILED</p> - Contract is now self-destructing");
serv.removeContract(contract);
} else {
post(`Contract <p style='color:red;display:inline'>FAILED</p> - ${contract.getMaxNumTries() - contract.tries} tries remaining`);
}
break;
case CodingContractResult.Cancelled:
@@ -2182,6 +2199,7 @@ let Terminal = {
break;
}
Terminal.contractOpen = false;
console.log(Terminal.contractOpen);
},
};
+32 -8
View File
@@ -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",
+53 -8
View File
@@ -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;
}