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": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-5.4.1.tgz",
"integrity": "sha512-XLmq3H/BVvW6/GbxKryGxWORz1ebilSsUDlyC27bXhWGWAZWkGwS6FLHjOlwFXNFoWFQEO/Df4u0YYd0K3BQgQ=="
"version": "5.7.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz",
"integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw=="
},
"acorn-dynamic-import": {
"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": {
"version": "5.5.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",

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

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

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

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

@ -321,7 +321,7 @@ function iTutorialEvaluateStep() {
Engine.loadActiveScriptsContent();
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 " +
"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.");
nextBtn.style.display = "none";

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

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

@ -18,8 +18,8 @@ import {
processHacknetEarnings
} from "./Hacknet/HacknetHelpers";
import { loadMessages, initMessages, Messages } from "./Message/MessageHelpers";
import { loadAllRunningScripts } from "./NetscriptWorker";
import { Player, loadPlayer } from "./Player";
import { loadAllRunningScripts } from "./Script/ScriptHelpers";
import { AllServers, loadAllServers } from "./Server/AllServers";
import { Settings } from "./Settings/Settings";
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
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 { 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,
// rather than NetscriptEvaluator. This is useful because NetscriptJS code does
// not work under NetscriptEvaluator.
async function parseOnlyRamCalculate(server, code, workerScript) {
async function parseOnlyRamCalculate(otherScripts, code, workerScript) {
try {
// Maps dependent identifiers to their dependencies.
//
@ -74,11 +74,25 @@ async function parseOnlyRamCalculate(server, code, workerScript) {
return -1;
}
} else {
const script = server.getScript(nextModule.startsWith("./") ? nextModule.slice(2) : nextModule);
if (!script) {
if (!Array.isArray(otherScripts)) {
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");
return -1; // No such script on the server.
}
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({
ImportDeclaration: (node, st, walkDeeper) => {
const importModuleName = node.source.value;
@ -311,19 +295,18 @@ function parseOnlyCalculateDeps(code, currentModule) {
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
// track of whatever's needed for RAM calculations
const workerScript = {
loadedFns: {},
serverIp: currServ.ip,
env: {
vars: RamCosts,
}
}
try {
return await parseOnlyRamCalculate(currServ, codeCopy, workerScript);
return await parseOnlyRamCalculate(otherScripts, codeCopy, workerScript);
} catch (e) {
console.error(`Failed to parse script for RAM calculations:`);
console.error(e);

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

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

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

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

@ -41,11 +41,11 @@ export function buyStock(stock: Stock, shares: number, workerScript: WorkerScrip
// Validate arguments
shares = Math.round(shares);
if (shares == 0 || shares < 0) { return false; }
if (shares <= 0) { return false; }
if (stock == null || isNaN(shares)) {
if (tixApi) {
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");
}
@ -58,7 +58,7 @@ export function buyStock(stock: Stock, shares: number, workerScript: WorkerScrip
if (Player.money.lt(totalPrice)) {
if (tixApi) {
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)}`);
}
@ -69,7 +69,7 @@ export function buyStock(stock: Stock, shares: number, workerScript: WorkerScrip
if (shares + stock.playerShares + stock.playerShortShares > stock.maxShares) {
if (tixApi) {
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.`);
}
@ -90,7 +90,7 @@ export function buyStock(stock: Stock, shares: number, workerScript: WorkerScrip
`Paid ${numeralWrapper.formatMoney(CONSTANTS.StockMarketCommission)} in commission fees.`
if (tixApi) {
if (workerScript!.shouldLog("buyStock")) { workerScript!.log(resultTxt); }
} else if (opts.suppressDialog != null && !opts.suppressDialog) {
} else if (opts.suppressDialog !== true) {
dialogBoxCreate(resultTxt);
}
@ -112,15 +112,15 @@ export function sellStock(stock: Stock, shares: number, workerScript: WorkerScri
if (stock == null || shares < 0 || isNaN(shares)) {
if (tixApi) {
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");
}
return false;
}
shares = Math.round(shares);
if (shares > stock.playerShares) {shares = stock.playerShares;}
if (shares === 0) {return false;}
if (shares > stock.playerShares) { shares = stock.playerShares; }
if (shares === 0) { return false; }
const gains = getSellTransactionGain(stock, shares, PositionTypes.Long);
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)}.`;
if (tixApi) {
if (workerScript!.shouldLog("sellStock")) { workerScript!.log(resultTxt); }
} else if (opts.suppressDialog != null && !opts.suppressDialog) {
} else if (opts.suppressDialog !== true) {
dialogBoxCreate(resultTxt);
}
@ -168,11 +168,11 @@ export function shortStock(stock: Stock, shares: number, workerScript: WorkerScr
// Validate arguments
shares = Math.round(shares);
if (shares === 0 || shares < 0) { return false; }
if (shares <= 0) { return false; }
if (stock == null || isNaN(shares)) {
if (tixApi) {
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 " +
"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 " +
"money to purchase this short position. 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 short position. You need " +
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 (tixApi) {
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.`);
}

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

@ -16,6 +16,8 @@ import { Stock } from "./Stock";
import { OrderTypes } from "./data/OrderTypes";
import { PositionTypes } from "./data/PositionTypes";
import { IMap } from "../types";
import { numeralWrapper } from "../ui/numeralFormat";
import { dialogBoxCreate } from "../../utils/DialogBox";
@ -23,6 +25,7 @@ import { dialogBoxCreate } from "../../utils/DialogBox";
interface IProcessOrderRefs {
rerenderFn: () => void;
stockMarket: IStockMarket;
symbolToStockMap: IMap<Stock>;
}
/**
@ -56,30 +59,30 @@ export function processOrders(stock: Stock, orderType: OrderTypes, posType: Posi
switch (order.type) {
case OrderTypes.LimitBuy:
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) {
executeOrder/*66*/(order, refs.stockMarket, refs.rerenderFn);
executeOrder/*66*/(order, refs);
}
break;
case OrderTypes.LimitSell:
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) {
executeOrder/*66*/(order, refs.stockMarket, refs.rerenderFn);
executeOrder/*66*/(order, refs);
}
break;
case OrderTypes.StopBuy:
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) {
executeOrder/*66*/(order, refs.stockMarket, refs.rerenderFn);
executeOrder/*66*/(order, refs);
}
break;
case OrderTypes.StopSell:
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) {
executeOrder/*66*/(order, refs.stockMarket, refs.rerenderFn);
executeOrder/*66*/(order, refs);
}
break;
default:
@ -95,24 +98,27 @@ export function processOrders(stock: Stock, orderType: OrderTypes, posType: Posi
* @param {Order} order - Order being executed
* @param {IStockMarket} stockMarket - Reference to StockMarket object
*/
function executeOrder(order: Order, stockMarket: IStockMarket, rerenderFn: () => void) {
const stock = order.stock;
function executeOrder(order: Order, refs: IProcessOrderRefs) {
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 stockOrders = orderBook[stock.symbol];
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
// emit popup dialog boxes. This options object configures the functions for that
const opts = {
rerenderFn: rerenderFn,
rerenderFn: refs.rerenderFn,
suppressDialog: true
}
let res = true;
let isBuy = false;
console.log("Executing the following order:");
console.log(order);
switch (order.type) {
case OrderTypes.LimitBuy: {
isBuy = true;
@ -122,8 +128,9 @@ function executeOrder(order: Order, stockMarket: IStockMarket, rerenderFn: () =>
const firstShares = Math.min(order.shares, stock.shareTxUntilMovement);
// First transaction to trigger movement
if (isLong ? buyStock(stock, firstShares, null, opts) : shortStock(stock, firstShares, null, opts)) {
limitShares = firstShares;
let res = (isLong ? buyStock(stock, firstShares, null, opts) : shortStock(stock, firstShares, null, opts));
if (res) {
sharesTransacted = firstShares;
} else {
break;
}
@ -138,16 +145,19 @@ function executeOrder(order: Order, stockMarket: IStockMarket, rerenderFn: () =>
}
const shares = Math.min(remainingShares, stock.shareTxForMovement);
if (isLong ? buyStock(stock, shares, null, opts) : shortStock(stock, shares, null, opts)) {
limitShares += shares;
let res = (isLong ? buyStock(stock, shares, null, opts) : shortStock(stock, shares, null, opts));
if (res) {
sharesTransacted += shares;
remainingShares -= shares;
} else {
break;
}
}
break;
}
case OrderTypes.StopBuy: {
isBuy = true;
sharesTransacted = order.shares;
if (order.pos === PositionTypes.Long) {
res = buyStock(stock, order.shares, null, opts) && res;
} else if (order.pos === PositionTypes.Short) {
@ -156,18 +166,23 @@ function executeOrder(order: Order, stockMarket: IStockMarket, rerenderFn: () =>
break;
}
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
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
if (isLong ? sellStock(stock, firstShares, null, opts) : sellShort(stock, firstShares, null, opts)) {
limitShares = firstShares;
sharesTransacted = firstShares;
} else {
break;
}
let remainingShares = order.shares - firstShares;
let remainingShares = totalShares - firstShares;
let remainingIterations = Math.ceil(remainingShares / stock.shareTxForMovement);
for (let i = 0; i < remainingIterations; ++i) {
if (isLong && stock.price < order.price) {
@ -178,18 +193,23 @@ function executeOrder(order: Order, stockMarket: IStockMarket, rerenderFn: () =>
const shares = Math.min(remainingShares, stock.shareTxForMovement);
if (isLong ? sellStock(stock, shares, null, opts) : sellShort(stock, shares, null, opts)) {
limitShares += shares;
sharesTransacted += shares;
remainingShares -= shares;
} else {
break;
}
}
break;
}
case OrderTypes.StopSell: {
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) {
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;
}
@ -198,42 +218,27 @@ function executeOrder(order: Order, stockMarket: IStockMarket, rerenderFn: () =>
return;
}
if (isLimit) {
res = (sharesTransacted > 0);
}
// Position type, for logging/message purposes
const pos = order.pos === PositionTypes.Long ? "Long" : "Short";
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) {
if (order == stockOrders[i]) {
if (isLimit) {
// Limit orders might only transact a certain # of shares, so we have the adjust the order qty.
stockOrders[i].shares -= limitShares;
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
// Limit orders might only transact a certain # of shares, so we have the adjust the order qty.
stockOrders[i].shares -= sharesTransacted;
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(order.shares)} shares transacted)`);
dialogBoxCreate(`${order.type} for ${stock.symbol} @ ${numeralWrapper.formatMoney(order.price)} (${pos}) was filled ` +
`(${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`);
}
rerenderFn();
refs.rerenderFn();
return;
}
}
@ -242,11 +247,11 @@ function executeOrder(order: Order, stockMarket: IStockMarket, rerenderFn: () =>
console.error(order);
} else {
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`);
} else {
dialogBoxCreate(`Failed to execute ${order.type} for ${order.stock.symbol} @ ${numeralWrapper.formatMoney(order.price)} (${pos}). ` +
`This is most likely a bug, please report to game developer with details.`);
dialogBoxCreate(`Failed to execute ${order.type} for ${stock.symbol} @ ${numeralWrapper.formatMoney(order.price)} (${pos}). ` +
`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) {
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 (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 {
dialogBoxCreate("ERROR: Invalid numeric value provided for either 'shares' or 'price' argument");
}
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) {
const orders = {};
for (const name in StockMarket) {
@ -66,8 +74,9 @@ export function placeOrder(stock, shares, price, type, position, workerScript=nu
const processOrderRefs = {
rerenderFn: displayStockMarketContent,
stockMarket: StockMarket,
symbolToStockMap: SymbolToStockMap,
}
processOrders(order.stock, order.type, order.pos, processOrderRefs);
processOrders(stock, order.type, order.pos, processOrderRefs);
displayStockMarketContent();
return true;
@ -78,9 +87,9 @@ export function cancelOrder(params, workerScript=null) {
var tixApi = (workerScript instanceof WorkerScript);
if (StockMarket["Orders"] == null) {return false;}
if (params.order && params.order instanceof Order) {
var order = params.order;
const order = params.order;
// 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) {
if (order == stockOrders[i]) {
stockOrders.splice(i, 1);
@ -122,6 +131,24 @@ export function loadStockMarket(saveString) {
StockMarket = {};
} else {
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 (Math.random() < thresh) {
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 = {
rerenderFn: displayStockMarketContent,
stockMarket: StockMarket,
symbolToStockMap: SymbolToStockMap,
}
if (c < chc) {
stock.price *= (1 + av);
stock.changePrice(stock.price * (1 + av));
processOrders(stock, OrderTypes.LimitBuy, PositionTypes.Short, processOrderRefs);
processOrders(stock, OrderTypes.LimitSell, PositionTypes.Long, processOrderRefs);
processOrders(stock, OrderTypes.StopBuy, PositionTypes.Long, processOrderRefs);
processOrders(stock, OrderTypes.StopSell, PositionTypes.Short, processOrderRefs);
} else {
stock.price /= (1 + av);
stock.changePrice(stock.price / (1 + av));
processOrders(stock, OrderTypes.LimitBuy, PositionTypes.Long, processOrderRefs);
processOrders(stock, OrderTypes.LimitSell, PositionTypes.Short, processOrderRefs);
processOrders(stock, OrderTypes.StopBuy, PositionTypes.Short, processOrderRefs);

@ -3,7 +3,7 @@ import { PositionTypes } from "./data/PositionTypes";
import { CONSTANTS } from "../Constants";
// 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
@ -118,7 +118,7 @@ export function processBuyTransactionPriceMovement(stock: Stock, shares: number,
if (stock.shareTxUntilMovement <= 0) {
stock.shareTxUntilMovement = stock.shareTxForMovement;
processPriceMovement();
stock.price = currPrice;
stock.changePrice(currPrice);
stock.otlkMag -= (forecastChangePerPriceMovement);
}
@ -140,11 +140,15 @@ export function processBuyTransactionPriceMovement(stock: Stock, shares: number,
stock.shareTxUntilMovement = stock.shareTxForMovement;
processPriceMovement();
}
stock.price = currPrice;
stock.changePrice(currPrice);
// Forecast always decreases in magnitude
const forecastChange = Math.min(5, forecastChangePerPriceMovement * (numIterations - 1));
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) {
stock.shareTxUntilMovement = stock.shareTxForMovement;
processPriceMovement();
stock.price = currPrice;
stock.changePrice(currPrice);
stock.otlkMag -= (forecastChangePerPriceMovement);
}
@ -263,11 +267,15 @@ export function processSellTransactionPriceMovement(stock: Stock, shares: number
stock.shareTxUntilMovement = stock.shareTxForMovement;
processPriceMovement();
}
stock.price = currPrice;
stock.changePrice(currPrice);
// Forecast always decreases in magnitude
const forecastChange = Math.min(5, forecastChangePerPriceMovement * (numIterations - 1));
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,
},
shareTxForMovement: {
max: 30e3,
min: 10e3,
max: 45e3,
min: 15e3,
},
symbol: StockSymbols[LocationName.AevumECorp],
},
@ -59,8 +59,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 1,
},
shareTxForMovement: {
max: 30e3,
min: 10e3,
max: 45e3,
min: 15e3,
},
symbol: StockSymbols[LocationName.Sector12MegaCorp],
},
@ -85,8 +85,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 1,
},
shareTxForMovement: {
max: 30e3,
min: 10e3,
max: 45e3,
min: 15e3,
},
symbol: StockSymbols[LocationName.Sector12BladeIndustries],
},
@ -111,8 +111,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 1,
},
shareTxForMovement: {
max: 30e3,
min: 10e3,
max: 45e3,
min: 15e3,
},
symbol: StockSymbols[LocationName.AevumClarkeIncorporated],
},
@ -137,8 +137,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 1,
},
shareTxForMovement: {
max: 30e3,
min: 10e3,
max: 45e3,
min: 15e3,
},
symbol: StockSymbols[LocationName.VolhavenOmniTekIncorporated],
},
@ -163,8 +163,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 1,
},
shareTxForMovement: {
max: 30e3,
min: 10e3,
max: 45e3,
min: 15e3,
},
symbol: StockSymbols[LocationName.Sector12FourSigma],
},
@ -189,8 +189,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 1,
},
shareTxForMovement: {
max: 30e3,
min: 10e3,
max: 45e3,
min: 15e3,
},
symbol: StockSymbols[LocationName.ChongqingKuaiGongInternational],
},
@ -215,8 +215,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 1,
},
shareTxForMovement: {
max: 30e3,
min: 10e3,
max: 45e3,
min: 15e3,
},
symbol: StockSymbols[LocationName.AevumFulcrumTechnologies],
},
@ -241,8 +241,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 2,
},
shareTxForMovement: {
max: 36e3,
min: 12e3,
max: 54e3,
min: 18e3,
},
symbol: StockSymbols[LocationName.IshimaStormTechnologies],
},
@ -267,8 +267,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 2,
},
shareTxForMovement: {
max: 36e3,
min: 12e3,
max: 54e3,
min: 18e3,
},
symbol: StockSymbols[LocationName.NewTokyoDefComm],
},
@ -293,8 +293,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 2,
},
shareTxForMovement: {
max: 36e3,
min: 12e3,
max: 54e3,
min: 18e3,
},
symbol: StockSymbols[LocationName.VolhavenHeliosLabs],
},
@ -319,8 +319,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 2,
},
shareTxForMovement: {
max: 36e3,
min: 12e3,
max: 54e3,
min: 18e3,
},
symbol: StockSymbols[LocationName.NewTokyoVitaLife],
},
@ -345,8 +345,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 3,
},
shareTxForMovement: {
max: 36e3,
min: 12e3,
max: 54e3,
min: 18e3,
},
symbol: StockSymbols[LocationName.Sector12IcarusMicrosystems],
},
@ -371,8 +371,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 2,
},
shareTxForMovement: {
max: 36e3,
min: 12e3,
max: 54e3,
min: 18e3,
},
symbol: StockSymbols[LocationName.Sector12UniversalEnergy],
},
@ -397,8 +397,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 3,
},
shareTxForMovement: {
max: 42e3,
min: 14e3,
max: 63e3,
min: 21e3,
},
symbol: StockSymbols[LocationName.AevumAeroCorp],
},
@ -423,8 +423,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 4,
},
shareTxForMovement: {
max: 42e3,
min: 14e3,
max: 63e3,
min: 21e3,
},
symbol: StockSymbols[LocationName.VolhavenOmniaCybersystems],
},
@ -449,8 +449,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 4,
},
shareTxForMovement: {
max: 42e3,
min: 14e3,
max: 63e3,
min: 21e3,
},
symbol: StockSymbols[LocationName.ChongqingSolarisSpaceSystems],
},
@ -475,8 +475,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 4,
},
shareTxForMovement: {
max: 42e3,
min: 14e3,
max: 63e3,
min: 21e3,
},
symbol: StockSymbols[LocationName.NewTokyoGlobalPharmaceuticals],
},
@ -501,8 +501,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 4,
},
shareTxForMovement: {
max: 42e3,
min: 14e3,
max: 63e3,
min: 21e3,
},
symbol: StockSymbols[LocationName.IshimaNovaMedical],
},
@ -527,8 +527,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 5,
},
shareTxForMovement: {
max: 18e3,
min: 4e3,
max: 27e3,
min: 6e3,
},
symbol: StockSymbols[LocationName.AevumWatchdogSecurity],
},
@ -553,8 +553,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 5,
},
shareTxForMovement: {
max: 36e3,
min: 12e3,
max: 54e3,
min: 18e3,
},
symbol: StockSymbols[LocationName.VolhavenLexoCorp],
},
@ -579,8 +579,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 3,
},
shareTxForMovement: {
max: 42e3,
min: 20e3,
max: 63e3,
min: 30e3,
},
symbol: StockSymbols[LocationName.AevumRhoConstruction],
},
@ -605,8 +605,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 5,
},
shareTxForMovement: {
max: 30e3,
min: 10e3,
max: 45e3,
min: 15e3,
},
symbol: StockSymbols[LocationName.Sector12AlphaEnterprises],
},
@ -631,8 +631,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 5,
},
shareTxForMovement: {
max: 30e3,
min: 10e3,
max: 45e3,
min: 15e3,
},
symbol: StockSymbols[LocationName.VolhavenSysCoreSecurities],
},
@ -657,8 +657,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 4,
},
shareTxForMovement: {
max: 42e3,
min: 20e3,
max: 63e3,
min: 30e3,
},
symbol: StockSymbols[LocationName.VolhavenCompuTek],
},
@ -683,8 +683,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 5,
},
shareTxForMovement: {
max: 18e3,
min: 6e3,
max: 27e3,
min: 9e3,
},
symbol: StockSymbols[LocationName.AevumNetLinkTechnologies],
},
@ -709,8 +709,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 4,
},
shareTxForMovement: {
max: 30e3,
min: 10e3,
max: 45e3,
min: 15e3,
},
symbol: StockSymbols[LocationName.IshimaOmegaSoftware],
},
@ -735,8 +735,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 6,
},
shareTxForMovement: {
max: 60e3,
min: 20e3,
max: 90e3,
min: 30e3,
},
symbol: StockSymbols[LocationName.Sector12FoodNStuff],
},
@ -761,8 +761,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 6,
},
shareTxForMovement: {
max: 28e3,
min: 8e3,
max: 42e3,
min: 12e3,
},
symbol: StockSymbols["Sigma Cosmetics"],
},
@ -787,8 +787,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 6,
},
shareTxForMovement: {
max: 21e3,
min: 6e3,
max: 32e3,
min: 9e3,
},
symbol: StockSymbols["Joes Guns"],
},
@ -813,8 +813,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 5,
},
shareTxForMovement: {
max: 24e3,
min: 8e3,
max: 36e3,
min: 12e3,
},
symbol: StockSymbols["Catalyst Ventures"],
},
@ -839,8 +839,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 3,
},
shareTxForMovement: {
max: 72e3,
min: 30e3,
max: 108e3,
min: 45e3,
},
symbol: StockSymbols["Microdyne Technologies"],
},
@ -865,8 +865,8 @@ export const InitStockMetadata: IConstructorParams[] = [
min: 2,
},
shareTxForMovement: {
max: 72e3,
min: 30e3,
max: 108e3,
min: 45e3,
},
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}`;
if (props.p.has4SData) {
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 = {

@ -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 { Order } from "../src/StockMarket/Order";
//import { processOrders } from "../src/StockMarket/OrderProcessing";
// import { Stock } from "../src/StockMarket/Stock";
import { Stock } from "../src/StockMarket/Stock";
/*
import {
deleteStockMarket,
initStockMarket,
@ -10,7 +11,7 @@ import {
StockMarket,
SymbolToStockMap,
} from "../src/StockMarket/StockMarket";
/*
*/
import {
calculateIncreasingPriceMovement,
calculateDecreasingPriceMovement,
@ -20,9 +21,8 @@ import {
processBuyTransactionPriceMovement,
processSellTransactionPriceMovement,
} from "../src/StockMarket/StockMarketHelpers";
*/
// import { OrderTypes } from "../src/StockMarket/data/OrderTypes"
// import { PositionTypes } from "../src/StockMarket/data/PositionTypes";
import { OrderTypes } from "../src/StockMarket/data/OrderTypes"
import { PositionTypes } from "../src/StockMarket/data/PositionTypes";
const assert = chai.assert;
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("Initialization", function() {
// Keeps track of initialized stocks. Contains their symbols
@ -218,6 +220,7 @@ describe("Stock Market Tests", function() {
});
});
*/
describe("Transaction Cost Calculator Functions", function() {
describe("getBuyTransactionCost()", function() {
@ -644,16 +647,16 @@ describe("Stock Market Tests", function() {
return new Order({}, 1, 1, OrderTypes.LimitBuy, PositionTypes.Long);
}
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() {
return new Order(new Stock(), 1, {}, OrderTypes.LimitBuy, PositionTypes.Short);
return new Order("FOO", 1, {}, OrderTypes.LimitBuy, PositionTypes.Short);
}
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() {
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();

@ -8,15 +8,16 @@
<body>
<div id="mocha"></div>
<script src="https://unpkg.com/chai/chai.js"></script>
<script src="https://unpkg.com/mocha/mocha.js"></script>
<script defer src="https://unpkg.com/chai/chai.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.checkLeaks();
</script>
<script type="module" src="tests.bundle.js"></script>
<script class="mocha-exec" type="module">
console.log("Running Tests");
mocha.run();
</script>
</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 = {};
entries[`${outputDirectory}/engine`] = "./src/engine.jsx";
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 {
@ -63,7 +74,7 @@ module.exports = (env, argv) => {
useShortDoctype: false
},
excludeChunks: [
"tests/tests"
"test/tests"
]
}),
new MiniCssExtractPlugin({
@ -128,6 +139,7 @@ module.exports = (env, argv) => {
devServer: {
port: 8000,
publicPath: `/`,
stats: statsConfig,
},
resolve: {
extensions: [
@ -136,6 +148,7 @@ module.exports = (env, argv) => {
".js",
".jsx",
]
}
},
stats: statsConfig,
};
};