Resolved more circular dependencies. Installed acorn-walk and imported it in RamCalculations using ES6 modules. Fixed bugs in stock market rework

This commit is contained in:
danielyxie 2019-05-06 18:01:06 -07:00
parent 8a5b6f6cbc
commit cdb5dfec62
34 changed files with 7246 additions and 305 deletions

11
package-lock.json generated

@ -671,9 +671,9 @@
} }
}, },
"acorn": { "acorn": {
"version": "5.4.1", "version": "5.7.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-5.4.1.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz",
"integrity": "sha512-XLmq3H/BVvW6/GbxKryGxWORz1ebilSsUDlyC27bXhWGWAZWkGwS6FLHjOlwFXNFoWFQEO/Df4u0YYd0K3BQgQ==" "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw=="
}, },
"acorn-dynamic-import": { "acorn-dynamic-import": {
"version": "2.0.2", "version": "2.0.2",
@ -707,6 +707,11 @@
} }
} }
}, },
"acorn-walk": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.1.tgz",
"integrity": "sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw=="
},
"ajv": { "ajv": {
"version": "5.5.2", "version": "5.5.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",

@ -9,8 +9,9 @@
"@types/numeral": "0.0.25", "@types/numeral": "0.0.25",
"@types/react": "^16.8.6", "@types/react": "^16.8.6",
"@types/react-dom": "^16.8.2", "@types/react-dom": "^16.8.2",
"acorn": "^5.0.0", "acorn": "^5.7.3",
"acorn-dynamic-import": "^2.0.0", "acorn-dynamic-import": "^2.0.0",
"acorn-walk": "^6.1.1",
"ajv": "^5.1.5", "ajv": "^5.1.5",
"ajv-keywords": "^2.0.0", "ajv-keywords": "^2.0.0",
"async": "^2.6.1", "async": "^2.6.1",

@ -61,7 +61,7 @@ export function purchaseHacknet() {
} }
} }
/* END INTERACTIVE TUTORIAL */ /* END INTERACTIVE TUTORIAL */
const numOwned = Player.hacknetNodes.length; const numOwned = Player.hacknetNodes.length;
if (hasHacknetServers()) { if (hasHacknetServers()) {
const cost = getCostOfNextHacknetServer(); const cost = getCostOfNextHacknetServer();

@ -9,7 +9,6 @@ import { IHacknetNode } from "./IHacknetNode";
import { CONSTANTS } from "../Constants"; import { CONSTANTS } from "../Constants";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers"; import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { IPlayer } from "../PersonObjects/IPlayer";
import { dialogBoxCreate } from "../../utils/DialogBox"; import { dialogBoxCreate } from "../../utils/DialogBox";
import { Generic_fromJSON, import { Generic_fromJSON,

@ -39,6 +39,8 @@ export class HacknetRoot extends React.Component {
} }
this.createHashUpgradesPopup = this.createHashUpgradesPopup.bind(this); this.createHashUpgradesPopup = this.createHashUpgradesPopup.bind(this);
this.handlePurchaseButtonClick = this.handlePurchaseButtonClick.bind(this);
this.recalculateTotalProduction = this.recalculateTotalProduction.bind(this);
} }
componentDidMount() { componentDidMount() {
@ -50,6 +52,12 @@ export class HacknetRoot extends React.Component {
createPopup(id, HashUpgradePopup, { popupId: id, rerender: this.createHashUpgradesPopup }); createPopup(id, HashUpgradePopup, { popupId: id, rerender: this.createHashUpgradesPopup });
} }
handlePurchaseButtonClick() {
if (purchaseHacknet() >= 0) {
this.recalculateTotalProduction();
}
}
recalculateTotalProduction() { recalculateTotalProduction() {
let total = 0; let total = 0;
for (let i = 0; i < Player.hacknetNodes.length; ++i) { for (let i = 0; i < Player.hacknetNodes.length; ++i) {
@ -85,13 +93,6 @@ export class HacknetRoot extends React.Component {
purchaseCost = getCostOfNextHacknetNode(); purchaseCost = getCostOfNextHacknetNode();
} }
// onClick event handler for purchase button
const purchaseOnClick = () => {
if (purchaseHacknet() >= 0) {
this.recalculateTotalProduction();
}
}
// onClick event handlers for purchase multiplier buttons // onClick event handlers for purchase multiplier buttons
const purchaseMultiplierOnClicks = [ const purchaseMultiplierOnClicks = [
this.setPurchaseMultiplier.bind(this, PurchaseMultipliers.x1), this.setPurchaseMultiplier.bind(this, PurchaseMultipliers.x1),
@ -112,7 +113,7 @@ export class HacknetRoot extends React.Component {
key={hserver.hostname} key={hserver.hostname}
node={hserver} node={hserver}
purchaseMultiplier={this.state.purchaseMultiplier} purchaseMultiplier={this.state.purchaseMultiplier}
recalculate={this.recalculateTotalProduction.bind(this)} recalculate={this.recalculateTotalProduction}
/> />
) )
} else { } else {
@ -121,7 +122,7 @@ export class HacknetRoot extends React.Component {
key={node.name} key={node.name}
node={node} node={node}
purchaseMultiplier={this.state.purchaseMultiplier} purchaseMultiplier={this.state.purchaseMultiplier}
recalculate={this.recalculateTotalProduction.bind(this)} recalculate={this.recalculateTotalProduction}
/> />
) )
} }
@ -132,7 +133,7 @@ export class HacknetRoot extends React.Component {
<h1>Hacknet Nodes</h1> <h1>Hacknet Nodes</h1>
<GeneralInfo /> <GeneralInfo />
<PurchaseButton cost={purchaseCost} multiplier={this.state.purchaseMultiplier} onClick={purchaseOnClick} /> <PurchaseButton cost={purchaseCost} multiplier={this.state.purchaseMultiplier} onClick={this.handlePurchaseButtonClick} />
<br /> <br />
<div id={"hacknet-nodes-money-multipliers-div"}> <div id={"hacknet-nodes-money-multipliers-div"}>

@ -321,7 +321,7 @@ function iTutorialEvaluateStep() {
Engine.loadActiveScriptsContent(); Engine.loadActiveScriptsContent();
iTutorialSetText("This page displays stats/information about all of your scripts that are " + iTutorialSetText("This page displays stats/information about all of your scripts that are " +
"running across every existing server. You can use this to gauge how well " + "running across every existing server. You can use this to gauge how well " +
"your scripts are doing. Let's go back to the Terminal now using the 'Terminal'" + "your scripts are doing. Let's go back to the Terminal now using the 'Terminal' " +
"link."); "link.");
nextBtn.style.display = "none"; nextBtn.style.display = "none";

@ -1145,8 +1145,7 @@ function NetscriptFunctions(workerScript) {
} }
// Create new script if it does not already exist // Create new script if it does not already exist
var newScript = new Script(); var newScript = new Script(scriptname);
newScript.filename = scriptname;
newScript.code = sourceScript.code; newScript.code = sourceScript.code;
newScript.ramUsage = sourceScript.ramUsage; newScript.ramUsage = sourceScript.ramUsage;
newScript.server = destServer.ip; newScript.server = destServer.ip;
@ -1932,12 +1931,12 @@ function NetscriptFunctions(workerScript) {
let script = workerScript.getScriptOnServer(fn); let script = workerScript.getScriptOnServer(fn);
if (script == null) { if (script == null) {
// Create a new script // Create a new script
script = new Script(fn, data, server.ip); script = new Script(fn, data, server.ip, server.scripts);
server.scripts.push(script); server.scripts.push(script);
return true; return true;
} }
mode === "w" ? script.code = data : script.code += data; mode === "w" ? script.code = data : script.code += data;
script.updateRamUsage(); script.updateRamUsage(server.scripts);
} else { } else {
// Write to text file // Write to text file
let txtFile = getTextFile(fn, server); let txtFile = getTextFile(fn, server);

@ -20,6 +20,8 @@ import {
import { NetscriptFunctions } from "./NetscriptFunctions"; import { NetscriptFunctions } from "./NetscriptFunctions";
import { executeJSScript } from "./NetscriptJSEvaluator"; import { executeJSScript } from "./NetscriptJSEvaluator";
import { NetscriptPort } from "./NetscriptPort"; import { NetscriptPort } from "./NetscriptPort";
import { Player } from "./Player";
import { RunningScript } from "./Script/RunningScript";
import { getRamUsageFromRunningScript } from "./Script/RunningScriptHelpers"; import { getRamUsageFromRunningScript } from "./Script/RunningScriptHelpers";
import { import {
findRunningScript, findRunningScript,

@ -18,8 +18,8 @@ import {
processHacknetEarnings processHacknetEarnings
} from "./Hacknet/HacknetHelpers"; } from "./Hacknet/HacknetHelpers";
import { loadMessages, initMessages, Messages } from "./Message/MessageHelpers"; import { loadMessages, initMessages, Messages } from "./Message/MessageHelpers";
import { loadAllRunningScripts } from "./NetscriptWorker";
import { Player, loadPlayer } from "./Player"; import { Player, loadPlayer } from "./Player";
import { loadAllRunningScripts } from "./Script/ScriptHelpers";
import { AllServers, loadAllServers } from "./Server/AllServers"; import { AllServers, loadAllServers } from "./Server/AllServers";
import { Settings } from "./Settings/Settings"; import { Settings } from "./Settings/Settings";
import { import {

@ -1 +1,3 @@
export declare function calculateRamUsage(codeCopy: string): number; import { Script } from "./Script";
export declare function calculateRamUsage(codeCopy: string, otherScripts: Script[]): number;

@ -1,5 +1,5 @@
// Calculate a script's RAM usage // Calculate a script's RAM usage
const walk = require("acorn/dist/walk"); // Importing this doesn't work for some reason. import * as walk from "acorn-walk";
import { RamCosts, RamCostConstants } from "../Netscript/RamCostGenerator"; import { RamCosts, RamCostConstants } from "../Netscript/RamCostGenerator";
import { parse, Node } from "../../utils/acorn"; import { parse, Node } from "../../utils/acorn";
@ -16,7 +16,7 @@ const memCheckGlobalKey = ".__GLOBAL__";
// Calcluates the amount of RAM a script uses. Uses parsing and AST walking only, // Calcluates the amount of RAM a script uses. Uses parsing and AST walking only,
// rather than NetscriptEvaluator. This is useful because NetscriptJS code does // rather than NetscriptEvaluator. This is useful because NetscriptJS code does
// not work under NetscriptEvaluator. // not work under NetscriptEvaluator.
async function parseOnlyRamCalculate(server, code, workerScript) { async function parseOnlyRamCalculate(otherScripts, code, workerScript) {
try { try {
// Maps dependent identifiers to their dependencies. // Maps dependent identifiers to their dependencies.
// //
@ -74,11 +74,25 @@ async function parseOnlyRamCalculate(server, code, workerScript) {
return -1; return -1;
} }
} else { } else {
const script = server.getScript(nextModule.startsWith("./") ? nextModule.slice(2) : nextModule); if (!Array.isArray(otherScripts)) {
if (!script) { console.warn(`parseOnlyRamCalculate() not called with array of scripts`);
return -1;
}
let script = null;
let fn = nextModule.startsWith("./") ? nextModule.slice(2) : nextModule;
for (const s of otherScripts) {
if (s.filename === fn) {
script = s;
break;
}
}
if (script == null) {
console.warn("Invalid script"); console.warn("Invalid script");
return -1; // No such script on the server. return -1; // No such script on the server.
} }
code = script.code; code = script.code;
} }
@ -251,36 +265,6 @@ function parseOnlyCalculateDeps(code, currentModule) {
} }
} }
//Spread syntax not supported in Edge yet, use Object.assign
/*
walk.recursive(ast, {key: globalKey}, {
ImportDeclaration: (node, st, walkDeeper) => {
const importModuleName = node.source.value;
additionalModules.push(importModuleName);
// This module's global scope refers to that module's global scope, no matter how we
// import it.
dependencyMap[st.key].add(importModuleName + memCheckGlobalKey);
for (let i = 0; i < node.specifiers.length; ++i) {
const spec = node.specifiers[i];
if (spec.imported !== undefined && spec.local !== undefined) {
// We depend on specific things.
internalToExternal[spec.local.name] = importModuleName + "." + spec.imported.name;
} else {
// We depend on everything.
dependencyMap[st.key].add(importModuleName + ".*");
}
}
},
FunctionDeclaration: (node, st, walkDeeper) => {
// Don't use walkDeeper, because we are changing the visitor set.
const key = currentModule + "." + node.id.name;
walk.recursive(node, {key: key}, commonVisitors());
},
...commonVisitors()
});
*/
walk.recursive(ast, {key: globalKey}, Object.assign({ walk.recursive(ast, {key: globalKey}, Object.assign({
ImportDeclaration: (node, st, walkDeeper) => { ImportDeclaration: (node, st, walkDeeper) => {
const importModuleName = node.source.value; const importModuleName = node.source.value;
@ -311,19 +295,18 @@ function parseOnlyCalculateDeps(code, currentModule) {
return {dependencyMap: dependencyMap, additionalModules: additionalModules}; return {dependencyMap: dependencyMap, additionalModules: additionalModules};
} }
export async function calculateRamUsage(codeCopy) { export async function calculateRamUsage(codeCopy, otherScripts) {
// We don't need a real WorkerScript for this. Just an object that keeps // We don't need a real WorkerScript for this. Just an object that keeps
// track of whatever's needed for RAM calculations // track of whatever's needed for RAM calculations
const workerScript = { const workerScript = {
loadedFns: {}, loadedFns: {},
serverIp: currServ.ip,
env: { env: {
vars: RamCosts, vars: RamCosts,
} }
} }
try { try {
return await parseOnlyRamCalculate(currServ, codeCopy, workerScript); return await parseOnlyRamCalculate(otherScripts, codeCopy, workerScript);
} catch (e) { } catch (e) {
console.error(`Failed to parse script for RAM calculations:`); console.error(`Failed to parse script for RAM calculations:`);
console.error(e); console.error(e);

@ -35,13 +35,13 @@ export class Script {
server: string = ""; server: string = "";
constructor(fn: string = "", code: string = "", server: string = "") { constructor(fn: string="", code: string="", server: string="", otherScripts: Script[]=[]) {
this.filename = fn; this.filename = fn;
this.code = code; this.code = code;
this.ramUsage = 0; this.ramUsage = 0;
this.server = server; // IP of server this script is on this.server = server; // IP of server this script is on
this.module = ""; this.module = "";
if (this.code !== "") {this.updateRamUsage();} if (this.code !== "") { this.updateRamUsage(otherScripts); }
}; };
download(): void { download(): void {
@ -64,7 +64,7 @@ export class Script {
} }
// Save a script FROM THE SCRIPT EDITOR // Save a script FROM THE SCRIPT EDITOR
saveScript(code: string, currServ: string): void { saveScript(code: string, serverIp: string, otherScripts: Script[]): void {
if (routing.isOn(Page.ScriptEditor)) { if (routing.isOn(Page.ScriptEditor)) {
//Update code and filename //Update code and filename
this.code = code.replace(/^\s+|\s+$/g, ''); this.code = code.replace(/^\s+|\s+$/g, '');
@ -77,21 +77,18 @@ export class Script {
this.filename = filenameElem!.value; this.filename = filenameElem!.value;
// Server // Server
this.server = currServ; this.server = serverIp;
//Calculate/update ram usage, execution time, etc. //Calculate/update ram usage, execution time, etc.
this.updateRamUsage(); this.updateRamUsage(otherScripts);
this.module = ""; this.module = "";
} }
} }
// Updates the script's RAM usage based on its code // Updates the script's RAM usage based on its code
async updateRamUsage() { async updateRamUsage(otherScripts: Script[]) {
// TODO Commented this out because I think its unnecessary var res = await calculateRamUsage(this.code, otherScripts);
// DOuble check/Test
// var codeCopy = this.code.repeat(1);
var res = await calculateRamUsage(this.code);
if (res !== -1) { if (res !== -1) {
this.ramUsage = roundToTwo(res); this.ramUsage = roundToTwo(res);
} }

@ -189,7 +189,7 @@ export async function updateScriptEditorContent() {
} }
var codeCopy = code.repeat(1); var codeCopy = code.repeat(1);
var ramUsage = await calculateRamUsage(codeCopy); var ramUsage = await calculateRamUsage(codeCopy, Player.getCurrentServer().scripts);
if (ramUsage !== -1) { if (ramUsage !== -1) {
scriptEditorRamText.innerText = "RAM: " + numeralWrapper.format(ramUsage, '0.00') + " GB"; scriptEditorRamText.innerText = "RAM: " + numeralWrapper.format(ramUsage, '0.00') + " GB";
} else { } else {
@ -236,15 +236,15 @@ function saveAndCloseScriptEditor() {
let s = Player.getCurrentServer(); let s = Player.getCurrentServer();
for (var i = 0; i < s.scripts.length; i++) { for (var i = 0; i < s.scripts.length; i++) {
if (filename == s.scripts[i].filename) { if (filename == s.scripts[i].filename) {
s.scripts[i].saveScript(getCurrentEditor().getCode(), Player.currentServer); s.scripts[i].saveScript(getCurrentEditor().getCode(), Player.currentServer, Player.getCurrentServer().scripts);
Engine.loadTerminalContent(); Engine.loadTerminalContent();
return iTutorialNextStep(); return iTutorialNextStep();
} }
} }
//If the current script does NOT exist, create a new one // If the current script does NOT exist, create a new one
let script = new Script(); let script = new Script();
script.saveScript(getCurrentEditor().getCode(), Player.currentServer); script.saveScript(getCurrentEditor().getCode(), Player.currentServer, Player.getCurrentServer().scripts);
s.scripts.push(script); s.scripts.push(script);
return iTutorialNextStep(); return iTutorialNextStep();
@ -272,7 +272,7 @@ function saveAndCloseScriptEditor() {
//If the current script already exists on the server, overwrite it //If the current script already exists on the server, overwrite it
for (var i = 0; i < s.scripts.length; i++) { for (var i = 0; i < s.scripts.length; i++) {
if (filename == s.scripts[i].filename) { if (filename == s.scripts[i].filename) {
s.scripts[i].saveScript(getCurrentEditor().getCode(), Player.currentServer); s.scripts[i].saveScript(getCurrentEditor().getCode(), Player.currentServer, Player.getCurrentServer().scripts);
Engine.loadTerminalContent(); Engine.loadTerminalContent();
return; return;
} }
@ -280,7 +280,7 @@ function saveAndCloseScriptEditor() {
//If the current script does NOT exist, create a new one //If the current script does NOT exist, create a new one
const script = new Script(); const script = new Script();
script.saveScript(getCurrentEditor().getCode(), Player.currentServer); script.saveScript(getCurrentEditor().getCode(), Player.currentServer, Player.getCurrentServer().scripts);
s.scripts.push(script); s.scripts.push(script);
} else if (filename.endsWith(".txt")) { } else if (filename.endsWith(".txt")) {
for (var i = 0; i < s.textFiles.length; ++i) { for (var i = 0; i < s.textFiles.length; ++i) {

@ -231,7 +231,7 @@ export class BaseServer {
if (fn === this.scripts[i].filename) { if (fn === this.scripts[i].filename) {
let script = this.scripts[i]; let script = this.scripts[i];
script.code = code; script.code = code;
script.updateRamUsage(); script.updateRamUsage(this.scripts);
script.module = ""; script.module = "";
ret.overwritten = true; ret.overwritten = true;
ret.success = true; ret.success = true;
@ -240,11 +240,7 @@ export class BaseServer {
} }
//Otherwise, create a new script //Otherwise, create a new script
const newScript = new Script(); const newScript = new Script(fn, code, this.ip, this.scripts);
newScript.filename = fn;
newScript.code = code;
newScript.updateRamUsage();
newScript.server = this.ip;
this.scripts.push(newScript); this.scripts.push(newScript);
ret.success = true; ret.success = true;
return ret; return ret;

@ -107,7 +107,7 @@ export function prestigeHomeComputer(homeComp: Server) {
//Update RAM usage on all scripts //Update RAM usage on all scripts
homeComp.scripts.forEach(function(script) { homeComp.scripts.forEach(function(script) {
script.updateRamUsage(); script.updateRamUsage(homeComp.scripts);
}); });
homeComp.messages.length = 0; //Remove .lit and .msg files homeComp.messages.length = 0; //Remove .lit and .msg files

@ -41,11 +41,11 @@ export function buyStock(stock: Stock, shares: number, workerScript: WorkerScrip
// Validate arguments // Validate arguments
shares = Math.round(shares); shares = Math.round(shares);
if (shares == 0 || shares < 0) { return false; } if (shares <= 0) { return false; }
if (stock == null || isNaN(shares)) { if (stock == null || isNaN(shares)) {
if (tixApi) { if (tixApi) {
workerScript!.log(`ERROR: buyStock() failed due to invalid arguments`); workerScript!.log(`ERROR: buyStock() failed due to invalid arguments`);
} else if (opts.suppressDialog != null && !opts.suppressDialog) { } else if (opts.suppressDialog !== true) {
dialogBoxCreate("Failed to buy stock. This may be a bug, contact developer"); dialogBoxCreate("Failed to buy stock. This may be a bug, contact developer");
} }
@ -58,7 +58,7 @@ export function buyStock(stock: Stock, shares: number, workerScript: WorkerScrip
if (Player.money.lt(totalPrice)) { if (Player.money.lt(totalPrice)) {
if (tixApi) { if (tixApi) {
workerScript!.log(`ERROR: buyStock() failed because you do not have enough money to purchase this potiion. You need ${numeralWrapper.formatMoney(totalPrice)}`); workerScript!.log(`ERROR: buyStock() failed because you do not have enough money to purchase this potiion. You need ${numeralWrapper.formatMoney(totalPrice)}`);
} else if (opts.suppressDialog != null && !opts.suppressDialog) { } else if (opts.suppressDialog !== true) {
dialogBoxCreate(`You do not have enough money to purchase this. You need ${numeralWrapper.formatMoney(totalPrice)}`); dialogBoxCreate(`You do not have enough money to purchase this. You need ${numeralWrapper.formatMoney(totalPrice)}`);
} }
@ -69,7 +69,7 @@ export function buyStock(stock: Stock, shares: number, workerScript: WorkerScrip
if (shares + stock.playerShares + stock.playerShortShares > stock.maxShares) { if (shares + stock.playerShares + stock.playerShortShares > stock.maxShares) {
if (tixApi) { if (tixApi) {
workerScript!.log(`ERROR: buyStock() failed because purchasing this many shares would exceed ${stock.symbol}'s maximum number of shares`); workerScript!.log(`ERROR: buyStock() failed because purchasing this many shares would exceed ${stock.symbol}'s maximum number of shares`);
} else if (opts.suppressDialog != null && !opts.suppressDialog) { } else if (opts.suppressDialog !== true) {
dialogBoxCreate(`You cannot purchase this many shares. ${stock.symbol} has a maximum of ${numeralWrapper.formatBigNumber(stock.maxShares)} shares.`); dialogBoxCreate(`You cannot purchase this many shares. ${stock.symbol} has a maximum of ${numeralWrapper.formatBigNumber(stock.maxShares)} shares.`);
} }
@ -90,7 +90,7 @@ export function buyStock(stock: Stock, shares: number, workerScript: WorkerScrip
`Paid ${numeralWrapper.formatMoney(CONSTANTS.StockMarketCommission)} in commission fees.` `Paid ${numeralWrapper.formatMoney(CONSTANTS.StockMarketCommission)} in commission fees.`
if (tixApi) { if (tixApi) {
if (workerScript!.shouldLog("buyStock")) { workerScript!.log(resultTxt); } if (workerScript!.shouldLog("buyStock")) { workerScript!.log(resultTxt); }
} else if (opts.suppressDialog != null && !opts.suppressDialog) { } else if (opts.suppressDialog !== true) {
dialogBoxCreate(resultTxt); dialogBoxCreate(resultTxt);
} }
@ -112,15 +112,15 @@ export function sellStock(stock: Stock, shares: number, workerScript: WorkerScri
if (stock == null || shares < 0 || isNaN(shares)) { if (stock == null || shares < 0 || isNaN(shares)) {
if (tixApi) { if (tixApi) {
workerScript!.log(`ERROR: sellStock() failed due to invalid arguments`); workerScript!.log(`ERROR: sellStock() failed due to invalid arguments`);
} else if (opts.suppressDialog != null && !opts.suppressDialog) { } else if (opts.suppressDialog !== true) {
dialogBoxCreate("Failed to sell stock. This is probably due to an invalid quantity. Otherwise, this may be a bug, contact developer"); dialogBoxCreate("Failed to sell stock. This is probably due to an invalid quantity. Otherwise, this may be a bug, contact developer");
} }
return false; return false;
} }
shares = Math.round(shares); shares = Math.round(shares);
if (shares > stock.playerShares) {shares = stock.playerShares;} if (shares > stock.playerShares) { shares = stock.playerShares; }
if (shares === 0) {return false;} if (shares === 0) { return false; }
const gains = getSellTransactionGain(stock, shares, PositionTypes.Long); const gains = getSellTransactionGain(stock, shares, PositionTypes.Long);
if (gains == null) { return false; } if (gains == null) { return false; }
@ -148,7 +148,7 @@ export function sellStock(stock: Stock, shares: number, workerScript: WorkerScri
`After commissions, you gained a total of ${numeralWrapper.formatMoney(gains)}.`; `After commissions, you gained a total of ${numeralWrapper.formatMoney(gains)}.`;
if (tixApi) { if (tixApi) {
if (workerScript!.shouldLog("sellStock")) { workerScript!.log(resultTxt); } if (workerScript!.shouldLog("sellStock")) { workerScript!.log(resultTxt); }
} else if (opts.suppressDialog != null && !opts.suppressDialog) { } else if (opts.suppressDialog !== true) {
dialogBoxCreate(resultTxt); dialogBoxCreate(resultTxt);
} }
@ -168,11 +168,11 @@ export function shortStock(stock: Stock, shares: number, workerScript: WorkerScr
// Validate arguments // Validate arguments
shares = Math.round(shares); shares = Math.round(shares);
if (shares === 0 || shares < 0) { return false; } if (shares <= 0) { return false; }
if (stock == null || isNaN(shares)) { if (stock == null || isNaN(shares)) {
if (tixApi) { if (tixApi) {
workerScript!.log("ERROR: shortStock() failed because of invalid arguments."); workerScript!.log("ERROR: shortStock() failed because of invalid arguments.");
} else if (opts.suppressDialog != null && !opts.suppressDialog) { } else if (opts.suppressDialog !== true) {
dialogBoxCreate("Failed to initiate a short position in a stock. This is probably " + dialogBoxCreate("Failed to initiate a short position in a stock. This is probably " +
"due to an invalid quantity. Otherwise, this may be a bug, so contact developer"); "due to an invalid quantity. Otherwise, this may be a bug, so contact developer");
} }
@ -187,7 +187,7 @@ export function shortStock(stock: Stock, shares: number, workerScript: WorkerScr
workerScript!.log("ERROR: shortStock() failed because you do not have enough " + workerScript!.log("ERROR: shortStock() failed because you do not have enough " +
"money to purchase this short position. You need " + "money to purchase this short position. You need " +
numeralWrapper.formatMoney(totalPrice)); numeralWrapper.formatMoney(totalPrice));
} else if (opts.suppressDialog != null && !opts.suppressDialog) { } else if (opts.suppressDialog !== true) {
dialogBoxCreate("You do not have enough money to purchase this short position. You need " + dialogBoxCreate("You do not have enough money to purchase this short position. You need " +
numeralWrapper.formatMoney(totalPrice)); numeralWrapper.formatMoney(totalPrice));
} }
@ -199,7 +199,7 @@ export function shortStock(stock: Stock, shares: number, workerScript: WorkerScr
if (shares + stock.playerShares + stock.playerShortShares > stock.maxShares) { if (shares + stock.playerShares + stock.playerShortShares > stock.maxShares) {
if (tixApi) { if (tixApi) {
workerScript!.log(`ERROR: shortStock() failed because purchasing this many short shares would exceed ${stock.symbol}'s maximum number of shares.`); workerScript!.log(`ERROR: shortStock() failed because purchasing this many short shares would exceed ${stock.symbol}'s maximum number of shares.`);
} else if (opts.suppressDialog != null && !opts.suppressDialog) { } else if (opts.suppressDialog !== true) {
dialogBoxCreate(`You cannot purchase this many shares. ${stock.symbol} has a maximum of ${stock.maxShares} shares.`); dialogBoxCreate(`You cannot purchase this many shares. ${stock.symbol} has a maximum of ${stock.maxShares} shares.`);
} }

@ -2,14 +2,13 @@
* Represents a Limit or Buy Order on the stock market. Does not represent * Represents a Limit or Buy Order on the stock market. Does not represent
* a Market Order since those are just executed immediately * a Market Order since those are just executed immediately
*/ */
import { Stock } from "./Stock";
import { OrderTypes } from "./data/OrderTypes"; import { OrderTypes } from "./data/OrderTypes";
import { PositionTypes } from "./data/PositionTypes"; import { PositionTypes } from "./data/PositionTypes";
import { import {
Generic_fromJSON, Generic_fromJSON,
Generic_toJSON, Generic_toJSON,
Reviver Reviver,
} from "../../utils/JSONReviver"; } from "../../utils/JSONReviver";
export class Order { export class Order {
@ -23,10 +22,10 @@ export class Order {
readonly pos: PositionTypes; readonly pos: PositionTypes;
readonly price: number; readonly price: number;
shares: number; shares: number;
readonly stock: Stock; readonly stockSymbol: string;
readonly type: OrderTypes; readonly type: OrderTypes;
constructor(stk: Stock = new Stock(), shares: number=0, price: number=0, typ: OrderTypes=OrderTypes.LimitBuy, pos: PositionTypes=PositionTypes.Long) { constructor(stockSymbol: string="", shares: number=0, price: number=0, typ: OrderTypes=OrderTypes.LimitBuy, pos: PositionTypes=PositionTypes.Long) {
// Validate arguments // Validate arguments
let invalidArgs: boolean = false; let invalidArgs: boolean = false;
if (typeof shares !== "number" || typeof price !== "number") { if (typeof shares !== "number" || typeof price !== "number") {
@ -35,14 +34,14 @@ export class Order {
if (isNaN(shares) || isNaN(price)) { if (isNaN(shares) || isNaN(price)) {
invalidArgs = true; invalidArgs = true;
} }
if (!(stk instanceof Stock)) { if (typeof stockSymbol !== "string") {
invalidArgs = true; invalidArgs = true;
} }
if (invalidArgs) { if (invalidArgs) {
throw new Error(`Invalid constructor paramters for Order`); throw new Error(`Invalid constructor paramters for Order`);
} }
this.stock = stk; this.stockSymbol = stockSymbol;
this.shares = shares; this.shares = shares;
this.price = price; this.price = price;
this.type = typ; this.type = typ;

@ -16,6 +16,8 @@ import { Stock } from "./Stock";
import { OrderTypes } from "./data/OrderTypes"; import { OrderTypes } from "./data/OrderTypes";
import { PositionTypes } from "./data/PositionTypes"; import { PositionTypes } from "./data/PositionTypes";
import { IMap } from "../types";
import { numeralWrapper } from "../ui/numeralFormat"; import { numeralWrapper } from "../ui/numeralFormat";
import { dialogBoxCreate } from "../../utils/DialogBox"; import { dialogBoxCreate } from "../../utils/DialogBox";
@ -23,6 +25,7 @@ import { dialogBoxCreate } from "../../utils/DialogBox";
interface IProcessOrderRefs { interface IProcessOrderRefs {
rerenderFn: () => void; rerenderFn: () => void;
stockMarket: IStockMarket; stockMarket: IStockMarket;
symbolToStockMap: IMap<Stock>;
} }
/** /**
@ -56,30 +59,30 @@ export function processOrders(stock: Stock, orderType: OrderTypes, posType: Posi
switch (order.type) { switch (order.type) {
case OrderTypes.LimitBuy: case OrderTypes.LimitBuy:
if (order.pos === PositionTypes.Long && stock.price <= order.price) { if (order.pos === PositionTypes.Long && stock.price <= order.price) {
executeOrder/*66*/(order, refs.stockMarket, refs.rerenderFn); executeOrder/*66*/(order, refs);
} else if (order.pos === PositionTypes.Short && stock.price >= order.price) { } else if (order.pos === PositionTypes.Short && stock.price >= order.price) {
executeOrder/*66*/(order, refs.stockMarket, refs.rerenderFn); executeOrder/*66*/(order, refs);
} }
break; break;
case OrderTypes.LimitSell: case OrderTypes.LimitSell:
if (order.pos === PositionTypes.Long && stock.price >= order.price) { if (order.pos === PositionTypes.Long && stock.price >= order.price) {
executeOrder/*66*/(order, refs.stockMarket, refs.rerenderFn); executeOrder/*66*/(order, refs);
} else if (order.pos === PositionTypes.Short && stock.price <= order.price) { } else if (order.pos === PositionTypes.Short && stock.price <= order.price) {
executeOrder/*66*/(order, refs.stockMarket, refs.rerenderFn); executeOrder/*66*/(order, refs);
} }
break; break;
case OrderTypes.StopBuy: case OrderTypes.StopBuy:
if (order.pos === PositionTypes.Long && stock.price >= order.price) { if (order.pos === PositionTypes.Long && stock.price >= order.price) {
executeOrder/*66*/(order, refs.stockMarket, refs.rerenderFn); executeOrder/*66*/(order, refs);
} else if (order.pos === PositionTypes.Short && stock.price <= order.price) { } else if (order.pos === PositionTypes.Short && stock.price <= order.price) {
executeOrder/*66*/(order, refs.stockMarket, refs.rerenderFn); executeOrder/*66*/(order, refs);
} }
break; break;
case OrderTypes.StopSell: case OrderTypes.StopSell:
if (order.pos === PositionTypes.Long && stock.price <= order.price) { if (order.pos === PositionTypes.Long && stock.price <= order.price) {
executeOrder/*66*/(order, refs.stockMarket, refs.rerenderFn); executeOrder/*66*/(order, refs);
} else if (order.pos === PositionTypes.Short && stock.price >= order.price) { } else if (order.pos === PositionTypes.Short && stock.price >= order.price) {
executeOrder/*66*/(order, refs.stockMarket, refs.rerenderFn); executeOrder/*66*/(order, refs);
} }
break; break;
default: default:
@ -95,24 +98,27 @@ export function processOrders(stock: Stock, orderType: OrderTypes, posType: Posi
* @param {Order} order - Order being executed * @param {Order} order - Order being executed
* @param {IStockMarket} stockMarket - Reference to StockMarket object * @param {IStockMarket} stockMarket - Reference to StockMarket object
*/ */
function executeOrder(order: Order, stockMarket: IStockMarket, rerenderFn: () => void) { function executeOrder(order: Order, refs: IProcessOrderRefs) {
const stock = order.stock; const stock = refs.symbolToStockMap[order.stockSymbol];
if (!(stock instanceof Stock)) {
console.error(`Could not find stock for this order: ${order.stockSymbol}`);
return;
}
const stockMarket = refs.stockMarket;
const orderBook = stockMarket["Orders"]; const orderBook = stockMarket["Orders"];
const stockOrders = orderBook[stock.symbol]; const stockOrders = orderBook[stock.symbol];
const isLimit = (order.type === OrderTypes.LimitBuy || order.type === OrderTypes.LimitSell); const isLimit = (order.type === OrderTypes.LimitBuy || order.type === OrderTypes.LimitSell);
let limitShares = 0; let sharesTransacted = 0;
// When orders are executed, the buying and selling functions shouldn't // When orders are executed, the buying and selling functions shouldn't
// emit popup dialog boxes. This options object configures the functions for that // emit popup dialog boxes. This options object configures the functions for that
const opts = { const opts = {
rerenderFn: rerenderFn, rerenderFn: refs.rerenderFn,
suppressDialog: true suppressDialog: true
} }
let res = true; let res = true;
let isBuy = false; let isBuy = false;
console.log("Executing the following order:");
console.log(order);
switch (order.type) { switch (order.type) {
case OrderTypes.LimitBuy: { case OrderTypes.LimitBuy: {
isBuy = true; isBuy = true;
@ -122,8 +128,9 @@ function executeOrder(order: Order, stockMarket: IStockMarket, rerenderFn: () =>
const firstShares = Math.min(order.shares, stock.shareTxUntilMovement); const firstShares = Math.min(order.shares, stock.shareTxUntilMovement);
// First transaction to trigger movement // First transaction to trigger movement
if (isLong ? buyStock(stock, firstShares, null, opts) : shortStock(stock, firstShares, null, opts)) { let res = (isLong ? buyStock(stock, firstShares, null, opts) : shortStock(stock, firstShares, null, opts));
limitShares = firstShares; if (res) {
sharesTransacted = firstShares;
} else { } else {
break; break;
} }
@ -138,16 +145,19 @@ function executeOrder(order: Order, stockMarket: IStockMarket, rerenderFn: () =>
} }
const shares = Math.min(remainingShares, stock.shareTxForMovement); const shares = Math.min(remainingShares, stock.shareTxForMovement);
if (isLong ? buyStock(stock, shares, null, opts) : shortStock(stock, shares, null, opts)) { let res = (isLong ? buyStock(stock, shares, null, opts) : shortStock(stock, shares, null, opts));
limitShares += shares; if (res) {
sharesTransacted += shares;
remainingShares -= shares; remainingShares -= shares;
} else { } else {
break; break;
} }
} }
break;
} }
case OrderTypes.StopBuy: { case OrderTypes.StopBuy: {
isBuy = true; isBuy = true;
sharesTransacted = order.shares;
if (order.pos === PositionTypes.Long) { if (order.pos === PositionTypes.Long) {
res = buyStock(stock, order.shares, null, opts) && res; res = buyStock(stock, order.shares, null, opts) && res;
} else if (order.pos === PositionTypes.Short) { } else if (order.pos === PositionTypes.Short) {
@ -156,18 +166,23 @@ function executeOrder(order: Order, stockMarket: IStockMarket, rerenderFn: () =>
break; break;
} }
case OrderTypes.LimitSell: { case OrderTypes.LimitSell: {
// TODO need to account for player's shares here
// We only execute limit orders until the price fails to match the order condition // We only execute limit orders until the price fails to match the order condition
const isLong = (order.pos === PositionTypes.Long); const isLong = (order.pos === PositionTypes.Long);
const firstShares = Math.min(order.shares, stock.shareTxUntilMovement); const totalShares = Math.min((isLong ? stock.playerShares : stock.playerShortShares), order.shares);
if (totalShares === 0) {
return; // Player has no shares
}
const firstShares = Math.min(totalShares, stock.shareTxUntilMovement);
// First transaction to trigger movement // First transaction to trigger movement
if (isLong ? sellStock(stock, firstShares, null, opts) : sellShort(stock, firstShares, null, opts)) { if (isLong ? sellStock(stock, firstShares, null, opts) : sellShort(stock, firstShares, null, opts)) {
limitShares = firstShares; sharesTransacted = firstShares;
} else { } else {
break; break;
} }
let remainingShares = order.shares - firstShares; let remainingShares = totalShares - firstShares;
let remainingIterations = Math.ceil(remainingShares / stock.shareTxForMovement); let remainingIterations = Math.ceil(remainingShares / stock.shareTxForMovement);
for (let i = 0; i < remainingIterations; ++i) { for (let i = 0; i < remainingIterations; ++i) {
if (isLong && stock.price < order.price) { if (isLong && stock.price < order.price) {
@ -178,18 +193,23 @@ function executeOrder(order: Order, stockMarket: IStockMarket, rerenderFn: () =>
const shares = Math.min(remainingShares, stock.shareTxForMovement); const shares = Math.min(remainingShares, stock.shareTxForMovement);
if (isLong ? sellStock(stock, shares, null, opts) : sellShort(stock, shares, null, opts)) { if (isLong ? sellStock(stock, shares, null, opts) : sellShort(stock, shares, null, opts)) {
limitShares += shares; sharesTransacted += shares;
remainingShares -= shares; remainingShares -= shares;
} else { } else {
break; break;
} }
} }
break;
} }
case OrderTypes.StopSell: { case OrderTypes.StopSell: {
if (order.pos === PositionTypes.Long) { if (order.pos === PositionTypes.Long) {
res = sellStock(stock, order.shares, null, opts) && res; sharesTransacted = Math.min(stock.playerShares, order.shares);
if (sharesTransacted <= 0) { return; }
res = sellStock(stock, sharesTransacted, null, opts) && res;
} else if (order.pos === PositionTypes.Short) { } else if (order.pos === PositionTypes.Short) {
res = sellShort(stock, order.shares, null, opts) && res; sharesTransacted = Math.min(stock.playerShortShares, order.shares);
if (sharesTransacted <= 0) { return; }
res = sellShort(stock, sharesTransacted, null, opts) && res;
} }
break; break;
} }
@ -198,42 +218,27 @@ function executeOrder(order: Order, stockMarket: IStockMarket, rerenderFn: () =>
return; return;
} }
if (isLimit) {
res = (sharesTransacted > 0);
}
// Position type, for logging/message purposes // Position type, for logging/message purposes
const pos = order.pos === PositionTypes.Long ? "Long" : "Short"; const pos = order.pos === PositionTypes.Long ? "Long" : "Short";
if (res) { if (res) {
if (isLimit) {
} else {
for (let i = 0; i < stockOrders.length; ++i) {
if (order == stockOrders[i]) {
}
}
}
for (let i = 0; i < stockOrders.length; ++i) { for (let i = 0; i < stockOrders.length; ++i) {
if (order == stockOrders[i]) { if (order == stockOrders[i]) {
if (isLimit) { // Limit orders might only transact a certain # of shares, so we have the adjust the order qty.
// Limit orders might only transact a certain # of shares, so we have the adjust the order qty. stockOrders[i].shares -= sharesTransacted;
stockOrders[i].shares -= limitShares; if (stockOrders[i].shares <= 0) {
if (stockOrders[i].shares <= 0) {
stockOrders.splice(i, 1);
dialogBoxCreate(`${order.type} for ${order.stock.symbol} @ ${numeralWrapper.formatMoney(order.price)} (${pos}) was filled ` +
`(${Math.round(limitShares)} shares`);
} else {
dialogBoxCreate(`${order.type} for ${order.stock.symbol} @ ${numeralWrapper.formatMoney(order.price)} (${pos}) was partially filled ` +
`(${Math.round(limitShares)} shares transacted, ${stockOrders[i].shares} shares remaining`);
}
} else {
// Stop orders will transact everything, so they can be removed completely
stockOrders.splice(i, 1); stockOrders.splice(i, 1);
dialogBoxCreate(`${order.type} for ${order.stock.symbol} @ ${numeralWrapper.formatMoney(order.price)} (${pos}) was filled ` + dialogBoxCreate(`${order.type} for ${stock.symbol} @ ${numeralWrapper.formatMoney(order.price)} (${pos}) was filled ` +
`(${Math.round(order.shares)} shares transacted)`); `(${numeralWrapper.formatBigNumber(Math.round(sharesTransacted))} share)`);
} else {
dialogBoxCreate(`${order.type} for ${stock.symbol} @ ${numeralWrapper.formatMoney(order.price)} (${pos}) was partially filled ` +
`(${numeralWrapper.formatBigNumber(Math.round(sharesTransacted))} shares transacted, ${stockOrders[i].shares} shares remaining`);
} }
refs.rerenderFn();
rerenderFn();
return; return;
} }
} }
@ -242,11 +247,11 @@ function executeOrder(order: Order, stockMarket: IStockMarket, rerenderFn: () =>
console.error(order); console.error(order);
} else { } else {
if (isBuy) { if (isBuy) {
dialogBoxCreate(`Failed to execute ${order.type} for ${order.stock.symbol} @ ${numeralWrapper.formatMoney(order.price)} (${pos}). ` + dialogBoxCreate(`Failed to execute ${order.type} for ${stock.symbol} @ ${numeralWrapper.formatMoney(order.price)} (${pos}). ` +
`This is most likely because you do not have enough money or the order would exceed the stock's maximum number of shares`); `This is most likely because you do not have enough money or the order would exceed the stock's maximum number of shares`);
} else { } else {
dialogBoxCreate(`Failed to execute ${order.type} for ${order.stock.symbol} @ ${numeralWrapper.formatMoney(order.price)} (${pos}). ` + dialogBoxCreate(`Failed to execute ${order.type} for ${stock.symbol} @ ${numeralWrapper.formatMoney(order.price)} (${pos}). ` +
`This is most likely a bug, please report to game developer with details.`); `This may be a bug, please report to game developer with details.`);
} }
} }
} }

@ -41,16 +41,24 @@ export let SymbolToStockMap = {}; // Maps symbol -> Stock object
export function placeOrder(stock, shares, price, type, position, workerScript=null) { export function placeOrder(stock, shares, price, type, position, workerScript=null) {
const tixApi = (workerScript instanceof WorkerScript); const tixApi = (workerScript instanceof WorkerScript);
if (!(stock instanceof Stock)) {
if (tixApi) {
workerScript.log(`ERROR: Invalid stock passed to placeOrder() function`);
} else {
dialogBoxCreate(`ERROR: Invalid stock passed to placeOrder() function`);
}
return false;
}
if (typeof shares !== "number" || typeof price !== "number") { if (typeof shares !== "number" || typeof price !== "number") {
if (tixApi) { if (tixApi) {
workerScript.scriptRef.log("ERROR: Invalid numeric value provided for either 'shares' or 'price' argument"); workerScript.log("ERROR: Invalid numeric value provided for either 'shares' or 'price' argument");
} else { } else {
dialogBoxCreate("ERROR: Invalid numeric value provided for either 'shares' or 'price' argument"); dialogBoxCreate("ERROR: Invalid numeric value provided for either 'shares' or 'price' argument");
} }
return false; return false;
} }
const order = new Order(stock, shares, price, type, position); const order = new Order(stock.symbol, shares, price, type, position);
if (StockMarket["Orders"] == null) { if (StockMarket["Orders"] == null) {
const orders = {}; const orders = {};
for (const name in StockMarket) { for (const name in StockMarket) {
@ -66,8 +74,9 @@ export function placeOrder(stock, shares, price, type, position, workerScript=nu
const processOrderRefs = { const processOrderRefs = {
rerenderFn: displayStockMarketContent, rerenderFn: displayStockMarketContent,
stockMarket: StockMarket, stockMarket: StockMarket,
symbolToStockMap: SymbolToStockMap,
} }
processOrders(order.stock, order.type, order.pos, processOrderRefs); processOrders(stock, order.type, order.pos, processOrderRefs);
displayStockMarketContent(); displayStockMarketContent();
return true; return true;
@ -78,9 +87,9 @@ export function cancelOrder(params, workerScript=null) {
var tixApi = (workerScript instanceof WorkerScript); var tixApi = (workerScript instanceof WorkerScript);
if (StockMarket["Orders"] == null) {return false;} if (StockMarket["Orders"] == null) {return false;}
if (params.order && params.order instanceof Order) { if (params.order && params.order instanceof Order) {
var order = params.order; const order = params.order;
// An 'Order' object is passed in // An 'Order' object is passed in
var stockOrders = StockMarket["Orders"][order.stock.symbol]; var stockOrders = StockMarket["Orders"][order.stockSymbol];
for (var i = 0; i < stockOrders.length; ++i) { for (var i = 0; i < stockOrders.length; ++i) {
if (order == stockOrders[i]) { if (order == stockOrders[i]) {
stockOrders.splice(i, 1); stockOrders.splice(i, 1);
@ -122,6 +131,24 @@ export function loadStockMarket(saveString) {
StockMarket = {}; StockMarket = {};
} else { } else {
StockMarket = JSON.parse(saveString, Reviver); StockMarket = JSON.parse(saveString, Reviver);
// Backwards compatibility for v0.47.0
const orderBook = StockMarket["Orders"];
if (orderBook != null) {
// For each order, set its 'stockSymbol' property equal to the
// symbol of its 'stock' property
for (const stockSymbol in orderBook) {
const ordersForStock = orderBook[stockSymbol];
if (Array.isArray(ordersForStock)) {
for (const order of ordersForStock) {
if (order instanceof Order && order.stock instanceof Stock) {
order.stockSymbol = order.stock.symbol;
}
}
}
}
console.log(`Converted Stock Market order book to v0.47.0 format`);
}
} }
} }
@ -175,7 +202,7 @@ export function stockMarketCycle() {
if (stock.b) { thresh = 0.4; } if (stock.b) { thresh = 0.4; }
if (Math.random() < thresh) { if (Math.random() < thresh) {
stock.b = !stock.b; stock.b = !stock.b;
if (stock.otlkMag < 10) { stock.otlkMag += 0.4; } if (stock.otlkMag < 10) { stock.otlkMag += 0.2; }
} }
} }
} }
@ -220,15 +247,16 @@ export function processStockPrices(numCycles=1) {
const processOrderRefs = { const processOrderRefs = {
rerenderFn: displayStockMarketContent, rerenderFn: displayStockMarketContent,
stockMarket: StockMarket, stockMarket: StockMarket,
symbolToStockMap: SymbolToStockMap,
} }
if (c < chc) { if (c < chc) {
stock.price *= (1 + av); stock.changePrice(stock.price * (1 + av));
processOrders(stock, OrderTypes.LimitBuy, PositionTypes.Short, processOrderRefs); processOrders(stock, OrderTypes.LimitBuy, PositionTypes.Short, processOrderRefs);
processOrders(stock, OrderTypes.LimitSell, PositionTypes.Long, processOrderRefs); processOrders(stock, OrderTypes.LimitSell, PositionTypes.Long, processOrderRefs);
processOrders(stock, OrderTypes.StopBuy, PositionTypes.Long, processOrderRefs); processOrders(stock, OrderTypes.StopBuy, PositionTypes.Long, processOrderRefs);
processOrders(stock, OrderTypes.StopSell, PositionTypes.Short, processOrderRefs); processOrders(stock, OrderTypes.StopSell, PositionTypes.Short, processOrderRefs);
} else { } else {
stock.price /= (1 + av); stock.changePrice(stock.price / (1 + av));
processOrders(stock, OrderTypes.LimitBuy, PositionTypes.Long, processOrderRefs); processOrders(stock, OrderTypes.LimitBuy, PositionTypes.Long, processOrderRefs);
processOrders(stock, OrderTypes.LimitSell, PositionTypes.Short, processOrderRefs); processOrders(stock, OrderTypes.LimitSell, PositionTypes.Short, processOrderRefs);
processOrders(stock, OrderTypes.StopBuy, PositionTypes.Short, processOrderRefs); processOrders(stock, OrderTypes.StopBuy, PositionTypes.Short, processOrderRefs);

@ -3,7 +3,7 @@ import { PositionTypes } from "./data/PositionTypes";
import { CONSTANTS } from "../Constants"; import { CONSTANTS } from "../Constants";
// Amount by which a stock's forecast changes during each price movement // Amount by which a stock's forecast changes during each price movement
export const forecastChangePerPriceMovement = 0.4; export const forecastChangePerPriceMovement = 0.1;
/** /**
* Given a stock, calculates the amount by which the stock price is multiplied * Given a stock, calculates the amount by which the stock price is multiplied
@ -118,7 +118,7 @@ export function processBuyTransactionPriceMovement(stock: Stock, shares: number,
if (stock.shareTxUntilMovement <= 0) { if (stock.shareTxUntilMovement <= 0) {
stock.shareTxUntilMovement = stock.shareTxForMovement; stock.shareTxUntilMovement = stock.shareTxForMovement;
processPriceMovement(); processPriceMovement();
stock.price = currPrice; stock.changePrice(currPrice);
stock.otlkMag -= (forecastChangePerPriceMovement); stock.otlkMag -= (forecastChangePerPriceMovement);
} }
@ -140,11 +140,15 @@ export function processBuyTransactionPriceMovement(stock: Stock, shares: number,
stock.shareTxUntilMovement = stock.shareTxForMovement; stock.shareTxUntilMovement = stock.shareTxForMovement;
processPriceMovement(); processPriceMovement();
} }
stock.price = currPrice; stock.changePrice(currPrice);
// Forecast always decreases in magnitude // Forecast always decreases in magnitude
const forecastChange = Math.min(5, forecastChangePerPriceMovement * (numIterations - 1)); const forecastChange = Math.min(5, forecastChangePerPriceMovement * (numIterations - 1));
stock.otlkMag -= forecastChange; stock.otlkMag -= forecastChange;
if (stock.otlkMag < 0) {
stock.b = !stock.b;
stock.otlkMag = Math.abs(stock.otlkMag);
}
} }
/** /**
@ -242,7 +246,7 @@ export function processSellTransactionPriceMovement(stock: Stock, shares: number
if (stock.shareTxUntilMovement <= 0) { if (stock.shareTxUntilMovement <= 0) {
stock.shareTxUntilMovement = stock.shareTxForMovement; stock.shareTxUntilMovement = stock.shareTxForMovement;
processPriceMovement(); processPriceMovement();
stock.price = currPrice; stock.changePrice(currPrice);
stock.otlkMag -= (forecastChangePerPriceMovement); stock.otlkMag -= (forecastChangePerPriceMovement);
} }
@ -263,11 +267,15 @@ export function processSellTransactionPriceMovement(stock: Stock, shares: number
stock.shareTxUntilMovement = stock.shareTxForMovement; stock.shareTxUntilMovement = stock.shareTxForMovement;
processPriceMovement(); processPriceMovement();
} }
stock.price = currPrice; stock.changePrice(currPrice);
// Forecast always decreases in magnitude // Forecast always decreases in magnitude
const forecastChange = Math.min(5, forecastChangePerPriceMovement * (numIterations - 1)); const forecastChange = Math.min(5, forecastChangePerPriceMovement * (numIterations - 1));
stock.otlkMag -= forecastChange; stock.otlkMag -= forecastChange;
if (stock.otlkMag < 0) {
stock.b = !stock.b;
stock.otlkMag = Math.abs(stock.otlkMag);
}
} }
/** /**

@ -33,8 +33,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 1, min: 1,
}, },
shareTxForMovement: { shareTxForMovement: {
max: 30e3, max: 45e3,
min: 10e3, min: 15e3,
}, },
symbol: StockSymbols[LocationName.AevumECorp], symbol: StockSymbols[LocationName.AevumECorp],
}, },
@ -59,8 +59,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 1, min: 1,
}, },
shareTxForMovement: { shareTxForMovement: {
max: 30e3, max: 45e3,
min: 10e3, min: 15e3,
}, },
symbol: StockSymbols[LocationName.Sector12MegaCorp], symbol: StockSymbols[LocationName.Sector12MegaCorp],
}, },
@ -85,8 +85,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 1, min: 1,
}, },
shareTxForMovement: { shareTxForMovement: {
max: 30e3, max: 45e3,
min: 10e3, min: 15e3,
}, },
symbol: StockSymbols[LocationName.Sector12BladeIndustries], symbol: StockSymbols[LocationName.Sector12BladeIndustries],
}, },
@ -111,8 +111,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 1, min: 1,
}, },
shareTxForMovement: { shareTxForMovement: {
max: 30e3, max: 45e3,
min: 10e3, min: 15e3,
}, },
symbol: StockSymbols[LocationName.AevumClarkeIncorporated], symbol: StockSymbols[LocationName.AevumClarkeIncorporated],
}, },
@ -137,8 +137,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 1, min: 1,
}, },
shareTxForMovement: { shareTxForMovement: {
max: 30e3, max: 45e3,
min: 10e3, min: 15e3,
}, },
symbol: StockSymbols[LocationName.VolhavenOmniTekIncorporated], symbol: StockSymbols[LocationName.VolhavenOmniTekIncorporated],
}, },
@ -163,8 +163,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 1, min: 1,
}, },
shareTxForMovement: { shareTxForMovement: {
max: 30e3, max: 45e3,
min: 10e3, min: 15e3,
}, },
symbol: StockSymbols[LocationName.Sector12FourSigma], symbol: StockSymbols[LocationName.Sector12FourSigma],
}, },
@ -189,8 +189,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 1, min: 1,
}, },
shareTxForMovement: { shareTxForMovement: {
max: 30e3, max: 45e3,
min: 10e3, min: 15e3,
}, },
symbol: StockSymbols[LocationName.ChongqingKuaiGongInternational], symbol: StockSymbols[LocationName.ChongqingKuaiGongInternational],
}, },
@ -215,8 +215,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 1, min: 1,
}, },
shareTxForMovement: { shareTxForMovement: {
max: 30e3, max: 45e3,
min: 10e3, min: 15e3,
}, },
symbol: StockSymbols[LocationName.AevumFulcrumTechnologies], symbol: StockSymbols[LocationName.AevumFulcrumTechnologies],
}, },
@ -241,8 +241,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 2, min: 2,
}, },
shareTxForMovement: { shareTxForMovement: {
max: 36e3, max: 54e3,
min: 12e3, min: 18e3,
}, },
symbol: StockSymbols[LocationName.IshimaStormTechnologies], symbol: StockSymbols[LocationName.IshimaStormTechnologies],
}, },
@ -267,8 +267,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 2, min: 2,
}, },
shareTxForMovement: { shareTxForMovement: {
max: 36e3, max: 54e3,
min: 12e3, min: 18e3,
}, },
symbol: StockSymbols[LocationName.NewTokyoDefComm], symbol: StockSymbols[LocationName.NewTokyoDefComm],
}, },
@ -293,8 +293,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 2, min: 2,
}, },
shareTxForMovement: { shareTxForMovement: {
max: 36e3, max: 54e3,
min: 12e3, min: 18e3,
}, },
symbol: StockSymbols[LocationName.VolhavenHeliosLabs], symbol: StockSymbols[LocationName.VolhavenHeliosLabs],
}, },
@ -319,8 +319,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 2, min: 2,
}, },
shareTxForMovement: { shareTxForMovement: {
max: 36e3, max: 54e3,
min: 12e3, min: 18e3,
}, },
symbol: StockSymbols[LocationName.NewTokyoVitaLife], symbol: StockSymbols[LocationName.NewTokyoVitaLife],
}, },
@ -345,8 +345,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 3, min: 3,
}, },
shareTxForMovement: { shareTxForMovement: {
max: 36e3, max: 54e3,
min: 12e3, min: 18e3,
}, },
symbol: StockSymbols[LocationName.Sector12IcarusMicrosystems], symbol: StockSymbols[LocationName.Sector12IcarusMicrosystems],
}, },
@ -371,8 +371,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 2, min: 2,
}, },
shareTxForMovement: { shareTxForMovement: {
max: 36e3, max: 54e3,
min: 12e3, min: 18e3,
}, },
symbol: StockSymbols[LocationName.Sector12UniversalEnergy], symbol: StockSymbols[LocationName.Sector12UniversalEnergy],
}, },
@ -397,8 +397,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 3, min: 3,
}, },
shareTxForMovement: { shareTxForMovement: {
max: 42e3, max: 63e3,
min: 14e3, min: 21e3,
}, },
symbol: StockSymbols[LocationName.AevumAeroCorp], symbol: StockSymbols[LocationName.AevumAeroCorp],
}, },
@ -423,8 +423,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 4, min: 4,
}, },
shareTxForMovement: { shareTxForMovement: {
max: 42e3, max: 63e3,
min: 14e3, min: 21e3,
}, },
symbol: StockSymbols[LocationName.VolhavenOmniaCybersystems], symbol: StockSymbols[LocationName.VolhavenOmniaCybersystems],
}, },
@ -449,8 +449,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 4, min: 4,
}, },
shareTxForMovement: { shareTxForMovement: {
max: 42e3, max: 63e3,
min: 14e3, min: 21e3,
}, },
symbol: StockSymbols[LocationName.ChongqingSolarisSpaceSystems], symbol: StockSymbols[LocationName.ChongqingSolarisSpaceSystems],
}, },
@ -475,8 +475,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 4, min: 4,
}, },
shareTxForMovement: { shareTxForMovement: {
max: 42e3, max: 63e3,
min: 14e3, min: 21e3,
}, },
symbol: StockSymbols[LocationName.NewTokyoGlobalPharmaceuticals], symbol: StockSymbols[LocationName.NewTokyoGlobalPharmaceuticals],
}, },
@ -501,8 +501,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 4, min: 4,
}, },
shareTxForMovement: { shareTxForMovement: {
max: 42e3, max: 63e3,
min: 14e3, min: 21e3,
}, },
symbol: StockSymbols[LocationName.IshimaNovaMedical], symbol: StockSymbols[LocationName.IshimaNovaMedical],
}, },
@ -527,8 +527,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 5, min: 5,
}, },
shareTxForMovement: { shareTxForMovement: {
max: 18e3, max: 27e3,
min: 4e3, min: 6e3,
}, },
symbol: StockSymbols[LocationName.AevumWatchdogSecurity], symbol: StockSymbols[LocationName.AevumWatchdogSecurity],
}, },
@ -553,8 +553,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 5, min: 5,
}, },
shareTxForMovement: { shareTxForMovement: {
max: 36e3, max: 54e3,
min: 12e3, min: 18e3,
}, },
symbol: StockSymbols[LocationName.VolhavenLexoCorp], symbol: StockSymbols[LocationName.VolhavenLexoCorp],
}, },
@ -579,8 +579,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 3, min: 3,
}, },
shareTxForMovement: { shareTxForMovement: {
max: 42e3, max: 63e3,
min: 20e3, min: 30e3,
}, },
symbol: StockSymbols[LocationName.AevumRhoConstruction], symbol: StockSymbols[LocationName.AevumRhoConstruction],
}, },
@ -605,8 +605,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 5, min: 5,
}, },
shareTxForMovement: { shareTxForMovement: {
max: 30e3, max: 45e3,
min: 10e3, min: 15e3,
}, },
symbol: StockSymbols[LocationName.Sector12AlphaEnterprises], symbol: StockSymbols[LocationName.Sector12AlphaEnterprises],
}, },
@ -631,8 +631,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 5, min: 5,
}, },
shareTxForMovement: { shareTxForMovement: {
max: 30e3, max: 45e3,
min: 10e3, min: 15e3,
}, },
symbol: StockSymbols[LocationName.VolhavenSysCoreSecurities], symbol: StockSymbols[LocationName.VolhavenSysCoreSecurities],
}, },
@ -657,8 +657,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 4, min: 4,
}, },
shareTxForMovement: { shareTxForMovement: {
max: 42e3, max: 63e3,
min: 20e3, min: 30e3,
}, },
symbol: StockSymbols[LocationName.VolhavenCompuTek], symbol: StockSymbols[LocationName.VolhavenCompuTek],
}, },
@ -683,8 +683,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 5, min: 5,
}, },
shareTxForMovement: { shareTxForMovement: {
max: 18e3, max: 27e3,
min: 6e3, min: 9e3,
}, },
symbol: StockSymbols[LocationName.AevumNetLinkTechnologies], symbol: StockSymbols[LocationName.AevumNetLinkTechnologies],
}, },
@ -709,8 +709,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 4, min: 4,
}, },
shareTxForMovement: { shareTxForMovement: {
max: 30e3, max: 45e3,
min: 10e3, min: 15e3,
}, },
symbol: StockSymbols[LocationName.IshimaOmegaSoftware], symbol: StockSymbols[LocationName.IshimaOmegaSoftware],
}, },
@ -735,8 +735,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 6, min: 6,
}, },
shareTxForMovement: { shareTxForMovement: {
max: 60e3, max: 90e3,
min: 20e3, min: 30e3,
}, },
symbol: StockSymbols[LocationName.Sector12FoodNStuff], symbol: StockSymbols[LocationName.Sector12FoodNStuff],
}, },
@ -761,8 +761,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 6, min: 6,
}, },
shareTxForMovement: { shareTxForMovement: {
max: 28e3, max: 42e3,
min: 8e3, min: 12e3,
}, },
symbol: StockSymbols["Sigma Cosmetics"], symbol: StockSymbols["Sigma Cosmetics"],
}, },
@ -787,8 +787,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 6, min: 6,
}, },
shareTxForMovement: { shareTxForMovement: {
max: 21e3, max: 32e3,
min: 6e3, min: 9e3,
}, },
symbol: StockSymbols["Joes Guns"], symbol: StockSymbols["Joes Guns"],
}, },
@ -813,8 +813,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 5, min: 5,
}, },
shareTxForMovement: { shareTxForMovement: {
max: 24e3, max: 36e3,
min: 8e3, min: 12e3,
}, },
symbol: StockSymbols["Catalyst Ventures"], symbol: StockSymbols["Catalyst Ventures"],
}, },
@ -839,8 +839,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 3, min: 3,
}, },
shareTxForMovement: { shareTxForMovement: {
max: 72e3, max: 108e3,
min: 30e3, min: 45e3,
}, },
symbol: StockSymbols["Microdyne Technologies"], symbol: StockSymbols["Microdyne Technologies"],
}, },
@ -865,8 +865,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 2, min: 2,
}, },
shareTxForMovement: { shareTxForMovement: {
max: 72e3, max: 108e3,
min: 30e3, min: 45e3,
}, },
symbol: StockSymbols["Titan Laboratories"], symbol: StockSymbols["Titan Laboratories"],
}, },

@ -24,7 +24,9 @@ export function StockTickerHeaderText(props: IProps): React.ReactElement {
let hdrText = `${stock.name}${" ".repeat(1 + TickerHeaderFormatData.longestName - stock.name.length + (TickerHeaderFormatData.longestSymbol - stock.symbol.length))}${stock.symbol} -${" ".repeat(10 - stockPriceFormat.length)}${stockPriceFormat}`; let hdrText = `${stock.name}${" ".repeat(1 + TickerHeaderFormatData.longestName - stock.name.length + (TickerHeaderFormatData.longestSymbol - stock.symbol.length))}${stock.symbol} -${" ".repeat(10 - stockPriceFormat.length)}${stockPriceFormat}`;
if (props.p.has4SData) { if (props.p.has4SData) {
hdrText += ` - Volatility: ${numeralWrapper.format(stock.mv, '0,0.00')}% - Price Forecast: `; hdrText += ` - Volatility: ${numeralWrapper.format(stock.mv, '0,0.00')}% - Price Forecast: `;
hdrText += (stock.b ? "+" : "-").repeat(Math.floor(stock.otlkMag / 10) + 1); let plusOrMinus = stock.b; // True for "+", false for "-"
if (stock.otlkMag < 0) { plusOrMinus = !plusOrMinus }
hdrText += (plusOrMinus ? "+" : "-").repeat(Math.floor(Math.abs(stock.otlkMag) / 10) + 1);
} }
let styleMarkup = { let styleMarkup = {

@ -0,0 +1,17 @@
/**
* TODO This should also test the calcualteRamUsage() function from
* /Script/RamCalculations but there's some issues with getting tests to run
* when any npm package is included in the build (/Script/RamCalculations includes
* walk from acorn).
*/
import { getRamCost } from "../../src/Netscript/RamCostGenerator";
//import { calculateRamUsage } from "../../src/Script/RamCalculations"
const assert = chai.assert;
const expect = chai.expect;
console.log("Beginning Netscript Static RAM Calculation/Generation Tests");
describe("Netscript Static RAM Calculation/Generation Tests", function() {
});

5
test/README.md Normal file

@ -0,0 +1,5 @@
# Unit Tests
This directory contains unit tests for Bitburner.
Unit tests use Mocha/Chai and are run through the browser. The Mocha/Chai
packages are sourced directly in HTML.

@ -1,7 +1,8 @@
import { CONSTANTS } from "../src/Constants"; import { CONSTANTS } from "../src/Constants";
import { Order } from "../src/StockMarket/Order"; import { Order } from "../src/StockMarket/Order";
//import { processOrders } from "../src/StockMarket/OrderProcessing"; //import { processOrders } from "../src/StockMarket/OrderProcessing";
// import { Stock } from "../src/StockMarket/Stock"; import { Stock } from "../src/StockMarket/Stock";
/*
import { import {
deleteStockMarket, deleteStockMarket,
initStockMarket, initStockMarket,
@ -10,7 +11,7 @@ import {
StockMarket, StockMarket,
SymbolToStockMap, SymbolToStockMap,
} from "../src/StockMarket/StockMarket"; } from "../src/StockMarket/StockMarket";
/* */
import { import {
calculateIncreasingPriceMovement, calculateIncreasingPriceMovement,
calculateDecreasingPriceMovement, calculateDecreasingPriceMovement,
@ -20,9 +21,8 @@ import {
processBuyTransactionPriceMovement, processBuyTransactionPriceMovement,
processSellTransactionPriceMovement, processSellTransactionPriceMovement,
} from "../src/StockMarket/StockMarketHelpers"; } from "../src/StockMarket/StockMarketHelpers";
*/ import { OrderTypes } from "../src/StockMarket/data/OrderTypes"
// import { OrderTypes } from "../src/StockMarket/data/OrderTypes" import { PositionTypes } from "../src/StockMarket/data/PositionTypes";
// import { PositionTypes } from "../src/StockMarket/data/PositionTypes";
const assert = chai.assert; const assert = chai.assert;
const expect = chai.expect; const expect = chai.expect;
@ -158,6 +158,8 @@ describe("Stock Market Tests", function() {
}); });
}); });
/*
// TODO These tests fail due to circular dependency errors
describe("StockMarket object", function() { describe("StockMarket object", function() {
describe("Initialization", function() { describe("Initialization", function() {
// Keeps track of initialized stocks. Contains their symbols // Keeps track of initialized stocks. Contains their symbols
@ -218,6 +220,7 @@ describe("Stock Market Tests", function() {
}); });
}); });
*/
describe("Transaction Cost Calculator Functions", function() { describe("Transaction Cost Calculator Functions", function() {
describe("getBuyTransactionCost()", function() { describe("getBuyTransactionCost()", function() {
@ -644,16 +647,16 @@ describe("Stock Market Tests", function() {
return new Order({}, 1, 1, OrderTypes.LimitBuy, PositionTypes.Long); return new Order({}, 1, 1, OrderTypes.LimitBuy, PositionTypes.Long);
} }
function invalid2() { function invalid2() {
return new Order(new Stock(), "z", 0, OrderTypes.LimitBuy, PositionTypes.Short); return new Order("FOO", "z", 0, OrderTypes.LimitBuy, PositionTypes.Short);
} }
function invalid3() { function invalid3() {
return new Order(new Stock(), 1, {}, OrderTypes.LimitBuy, PositionTypes.Short); return new Order("FOO", 1, {}, OrderTypes.LimitBuy, PositionTypes.Short);
} }
function invalid4() { function invalid4() {
return new Order(new Stock(), 1, NaN, OrderTypes.LimitBuy, PositionTypes.Short); return new Order("FOO", 1, NaN, OrderTypes.LimitBuy, PositionTypes.Short);
} }
function invalid5() { function invalid5() {
return new Order(new Stock(), NaN, 0, OrderTypes.LimitBuy, PositionTypes.Short); return new Order("FOO", NaN, 0, OrderTypes.LimitBuy, PositionTypes.Short);
} }
expect(invalid1).to.throw(); expect(invalid1).to.throw();

@ -8,15 +8,16 @@
<body> <body>
<div id="mocha"></div> <div id="mocha"></div>
<script src="https://unpkg.com/chai/chai.js"></script> <script defer src="https://unpkg.com/chai/chai.js"></script>
<script src="https://unpkg.com/mocha/mocha.js"></script> <script defer src="https://unpkg.com/mocha/mocha.js"></script>
<script class="mocha-init"> <script type="module" class="mocha-init">
mocha.setup('bdd'); mocha.setup('bdd');
mocha.checkLeaks(); mocha.checkLeaks();
</script> </script>
<script type="module" src="tests.bundle.js"></script> <script type="module" src="tests.bundle.js"></script>
<script class="mocha-exec" type="module"> <script class="mocha-exec" type="module">
console.log("Running Tests");
mocha.run(); mocha.run();
</script> </script>
</body> </body>

2
test/index.js Normal file

@ -0,0 +1,2 @@
export * from "./Netscript/StaticRamCalculationTests";
export * from "./StockMarketTests";

2035
test/tests.bundle.js Normal file

File diff suppressed because it is too large Load Diff

1
test/tests.bundle.js.map Normal file

File diff suppressed because one or more lines are too long

4889
test/tests.css Normal file

File diff suppressed because it is too large Load Diff

1
test/tests.css.map Normal file

File diff suppressed because one or more lines are too long

@ -1,52 +0,0 @@
import {executeJSScript} from "../src/NetscriptJSEvaluator.js";
import {WorkerScript} from "../src/NetscriptWorker.js";
const chai = require("chai");
const chaiAsPromised = require("chai-as-promised");
chai.should();
chai.use(chaiAsPromised);
console.info('asdf');
describe('NSJS ScriptStore', function() {
it('should run an imported function', async function() {
const s = { filename: "", code: "export function main() { return 2; }", args:[]};
const worker = new WorkerScript(s);
chai.expect(await executeJSScript([], s)).to.equal(2);
});
/*
it('should handle recursive imports', async function() {
const s1 = { filename: "s1.js", code: "export function iAmRecursiveImport(x) { return x + 2; }" };
const s2 = { filename: "", code: `
import {iAmRecursiveImport} from \"s1.js\";
export function main() { return iAmRecursiveImport(3);
}`};
chai.expect(await executeJSScript([s1, s2], s2)).to.equal(5);
});
it (`should correctly reference the passed global env`, async function() {
var [x, y] = [0, 0];
var env = {
updateX: function(value) { x = value; },
updateY: function(value) { y = value; },
};
const s1 = {filename: "s1.js", code: "export function importedFn(x) { updateX(x); }"};
const s2 = {filename: "s2.js", code: `
import {importedFn} from "s1.js";
export function main() { updateY(7); importedFn(3); }
`}
await executeJSScript(s2, [s1, s2], env);
chai.expect(y).to.equal(7);
chai.expect(x).to.equal(3);
});
it (`should throw on circular dep`, async function() {
const s1 = {filename: "s1.js", code: "import \"s2.js\""};
const s2 = {filename: "s2.js", code: `
import * as s1 from "s1.js";
export function main() {}
`}
executeJSScript(s2, [s1, s2]).should.eventually.throw();
});*/
});

@ -1 +0,0 @@
export * from "./StockMarketTests";

@ -10,7 +10,18 @@ module.exports = (env, argv) => {
const entries = {}; const entries = {};
entries[`${outputDirectory}/engine`] = "./src/engine.jsx"; entries[`${outputDirectory}/engine`] = "./src/engine.jsx";
if (!isDevServer) { if (!isDevServer) {
entries["tests/tests"] = "./tests/index.js"; entries["test/tests"] = "./test/index.js";
}
const statsConfig = {
builtAt: true,
children: false,
chunks: false,
chunkGroups: false,
chunkModules: false,
chunkOrigins: false,
colors: true,
entrypoints: true,
} }
return { return {
@ -63,7 +74,7 @@ module.exports = (env, argv) => {
useShortDoctype: false useShortDoctype: false
}, },
excludeChunks: [ excludeChunks: [
"tests/tests" "test/tests"
] ]
}), }),
new MiniCssExtractPlugin({ new MiniCssExtractPlugin({
@ -128,6 +139,7 @@ module.exports = (env, argv) => {
devServer: { devServer: {
port: 8000, port: 8000,
publicPath: `/`, publicPath: `/`,
stats: statsConfig,
}, },
resolve: { resolve: {
extensions: [ extensions: [
@ -136,6 +148,7 @@ module.exports = (env, argv) => {
".js", ".js",
".jsx", ".jsx",
] ]
} },
stats: statsConfig,
}; };
}; };