diff --git a/doc/source/basicgameplay/codingcontracts.rst b/doc/source/basicgameplay/codingcontracts.rst index a1e16ada8..f4f653250 100644 --- a/doc/source/basicgameplay/codingcontracts.rst +++ b/doc/source/basicgameplay/codingcontracts.rst @@ -113,7 +113,7 @@ The list contains the name of (i.e. the value returned by | | | to any position from i to i+n. | | | | | | | | Assuming you are initially positioned at the start of the array, determine | -| | | whether you are able to reach the last index of the array EXACTLY. | +| | | whether you are able to reach the last index of the array. | +------------------------------------+------------------------------------------------------------------------------------------+ | Merge Overlapping Intervals | | Given an array of intervals, merge all overlapping intervals. An interval | | | | is an array with two numbers, where the first number is always less than | diff --git a/doc/source/basicgameplay/infiltration.rst b/doc/source/basicgameplay/infiltration.rst index 97201cada..ae3221107 100644 --- a/doc/source/basicgameplay/infiltration.rst +++ b/doc/source/basicgameplay/infiltration.rst @@ -17,9 +17,16 @@ says 'Infiltrate Company'. When infiltrating a company you will be presented with short active challenges. None of the challenges use the mouse. -The difficulty at the top lowers with better combat stats. It is not recommended +The difficulty at the top lowers with better combat stats and charisma. It is not recommended to attempt infiltrations above mid-normal. +The "maximum level" is the number of challenges you will need to pass to receive +the infiltration reward. + +Every time you fail an infiltration challenge, you will take damage based on the +difficulty of the infiltration. If you are reduced to 0 hp or below, the +infiltration will immediately end. + * Most use spacebar as "action" * Some use WASD or arrows interchangeably. * A few others use the rest of the keyboard. @@ -60,4 +67,4 @@ Then move the cursor and press space to mark the mines on the board. ** Cut the wires ** Follow the instructions and press the numbers 1 through 9 to cut the appropriate -wires. \ No newline at end of file +wires. diff --git a/doc/source/basicgameplay/stats.rst b/doc/source/basicgameplay/stats.rst index ef2be4de3..0df67b5a9 100644 --- a/doc/source/basicgameplay/stats.rst +++ b/doc/source/basicgameplay/stats.rst @@ -16,7 +16,6 @@ Affects: * Chance to successfully hack a server * Percent money stolen when hacking a server * Success rate of certain crimes -* Success rate of Hacking option during Infiltration * Time it takes to create a program * Faction reputation gain when carrying out Hacking Contracts or Field Work * Company reputation gain for certain jobs @@ -26,7 +25,6 @@ Gain experience by: * Manually hacking servers through Terminal * Executing hack(), grow(), or weaken() through a script * Committing certain crimes -* Infiltration * Carrying out Hacking Contracts or doing Field work for Factions * Working certain jobs at a company * Studying at a university @@ -38,14 +36,12 @@ Represents the player's physical offensive power Affects: * Success rate of certain crimes -* Success rate of Combat options during Infiltration * Faction reputation gain for Security and Field Work * Company reputation gain for certain jobs Gain experience by: * Committing certain crimes -* Infiltration * Working out at a gym * Doing Security/Field Work for a faction * Working certain jobs at a company @@ -58,15 +54,12 @@ Affects: * Success rate of certain crimes * The player's HP -* Success rate of Combat options during Infiltration -* How much damage the player takes during Infiltration * Faction reputation gain for Security and Field Work * Company reputation gain for certain jobs Gain experience by: * Committing certain crimes -* Infiltration * Working out at a gym * Doing Security/Field Work for a faction * Working certain jobs at a company @@ -78,14 +71,12 @@ Represents the player's skill and adeptness in performing certain tasks Affects: * Success rate of certain crimes -* Success rate of Combat, Lockpick, and Escape options during Infiltration * Faction reputation gain for Security and Field Work * Company reputation gain for certain jobs Gain experience by: * Committing certain crimes -* Infiltration * Working out at a gym * Doing Security/Field Work for a faction * Working certain jobs at a company @@ -97,14 +88,12 @@ Represents the player's speed and ability to move Affects: * Success rate of certain crimes -* Success rate of Combat, Sneak, and Escape options during Infiltration * Faction reputation gain for Security and Field Work * Company reputation gain for certain jobs Gain experience by: * Committing certain crimes -* Infiltration * Working out at a gym * Doing Security/Field Work for a faction * Working certain jobs at a company @@ -116,14 +105,12 @@ Represents the player's social abilities Affects: * Success rate of certain crimes -* Success rate of Bribe option during Infiltration * Faction reputation gain for Field Work * Company reputation gain for most jobs Gain experience by: * Committing certain crimes -* Infiltration * Studying at a university * Working a relevant job at a company * Doing Field work for a Faction diff --git a/doc/source/guidesandtips/gettingstartedguideforbeginnerprogrammers.rst b/doc/source/guidesandtips/gettingstartedguideforbeginnerprogrammers.rst index 230d20bca..83fca99be 100644 --- a/doc/source/guidesandtips/gettingstartedguideforbeginnerprogrammers.rst +++ b/doc/source/guidesandtips/gettingstartedguideforbeginnerprogrammers.rst @@ -200,52 +200,52 @@ Here's what mine showed at the time I made this:: --Root Access: YES, Required hacking skill: 1 --Number of open ports required to NUKE: 0 --RAM: 4.00GB - + ----zer0 ------Root Access: NO, Required hacking skill: 75 ------Number of open ports required to NUKE: 1 ------RAM: 32.00GB - + foodnstuff --Root Access: NO, Required hacking skill: 1 --Number of open ports required to NUKE: 0 --RAM: 16.00GB - + sigma-cosmetics --Root Access: NO, Required hacking skill: 5 --Number of open ports required to NUKE: 0 --RAM: 16.00GB - + joesguns --Root Access: NO, Required hacking skill: 10 --Number of open ports required to NUKE: 0 --RAM: 16.00GB - + ----max-hardware ------Root Access: NO, Required hacking skill: 80 ------Number of open ports required to NUKE: 1 ------RAM: 32.00GB - + ----CSEC ------Root Access: NO, Required hacking skill: 54 ------Number of open ports required to NUKE: 1 ------RAM: 8.00GB - + hong-fang-tea --Root Access: NO, Required hacking skill: 30 --Number of open ports required to NUKE: 0 --RAM: 16.00GB - + ----nectar-net ------Root Access: NO, Required hacking skill: 20 ------Number of open ports required to NUKE: 0 ------RAM: 16.00GB - + harakiri-sushi --Root Access: NO, Required hacking skill: 40 --Number of open ports required to NUKE: 0 --RAM: 16.00GB - + iron-gym --Root Access: NO, Required hacking skill: 100 --Number of open ports required to NUKE: 1 @@ -806,8 +806,7 @@ startup script. Feel free to adjust it to your liking. // Array of all servers that don't need any ports opened // to gain root access. These have 16 GB of RAM - var servers0Port = ["n00dles", - "sigma-cosmetics", + var servers0Port = ["sigma-cosmetics", "joesguns", "nectar-net", "hong-fang-tea", diff --git a/doc/source/netscript/netscriptmisc.rst b/doc/source/netscript/netscriptmisc.rst index ddbee4088..e41bfc0cf 100644 --- a/doc/source/netscript/netscriptmisc.rst +++ b/doc/source/netscript/netscriptmisc.rst @@ -59,15 +59,14 @@ And the data in port 1 will look like:: .. warning:: In :ref:`netscriptjs`, do not trying writing base `Promises `_ - to a port. + to a port. **Port Handles** WARNING: Port Handles only work in :ref:`netscriptjs`. They do not work in :ref:`netscript1` The :js:func:`getPortHandle` Netscript function can be used to get a handle to a Netscript Port. -This handle allows you to access several new port-related functions and the -port's underlying data structure, which is just a JavaScript array. The functions are: +This handle allows you to access several new port-related functions. The functions are: .. js:method:: NetscriptPort.writePort(data) @@ -111,22 +110,11 @@ port's underlying data structure, which is just a JavaScript array. The function Clears all data from the port. Works the same as the Netscript function `clear` -.. js:attribute:: NetscriptPort.data - - The Netscript port underlying data structure, which is just a Javascript array. All - valid Javascript Array methods can be called on this. - Port Handle Example:: port = getPortHandle(5); back = port.data.pop(); //Get and remove last element in port - //Remove an element from the port - i = port.data.findIndex("foo"); - if (i != -1) { - port.data.slice(i, 1); - } - //Wait for port data before reading while(port.empty()) { sleep(10000); diff --git a/src/Hacknet/HacknetNode.ts b/src/Hacknet/HacknetNode.ts index 4982b606b..5cb950232 100644 --- a/src/Hacknet/HacknetNode.ts +++ b/src/Hacknet/HacknetNode.ts @@ -18,8 +18,18 @@ import { HacknetNodeConstants } from "./data/Constants"; import { dialogBoxCreate } from "../ui/React/DialogBox"; import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver"; +import { ObjectValidator, minMax } from "../utils/Validator"; export class HacknetNode implements IHacknetNode { + + static validationData: ObjectValidator = { + cores: minMax(1, 1, HacknetNodeConstants.MaxCores), + level: minMax(1, 1, HacknetNodeConstants.MaxLevel), + ram: minMax(1, 1, HacknetNodeConstants.MaxRam), + onlineTimeSeconds: minMax(0, 0, Infinity), + totalMoneyGenerated: minMax(0, 0, Infinity) + } + // Node's number of cores cores = 1; diff --git a/src/ScriptEditor/NetscriptDefinitions.d.ts b/src/ScriptEditor/NetscriptDefinitions.d.ts index cc33b1ec0..543017be0 100644 --- a/src/ScriptEditor/NetscriptDefinitions.d.ts +++ b/src/ScriptEditor/NetscriptDefinitions.d.ts @@ -109,32 +109,6 @@ interface RunningScript { threads: number; } -/** - * Interface of a netscript port - * @public - */ -export interface IPort { - /** write data to the port and removes and returns first element if full */ - write: (value: any) => any; - /** add data to port if not full. - * @returns true if added and false if full and not added */ - tryWrite: (value: any) => boolean; - /** reads and removes first element from port - * if no data in port returns "NULL PORT DATA" - */ - read: () => any; - /** reads first element without removing it from port - * if no data in port returns "NULL PORT DATA" - */ - peek: () => any; - /** check if port is full */ - full: () => boolean; - /** check if port is empty */ - empty: () => boolean; - /** removes all data from port */ - clear: () => void; -} - /** * Data representing the internal values of a crime. * @public @@ -280,6 +254,24 @@ export interface AugmentPair { cost: number; } +/** + * @public + */ +export enum PositionTypes { + Long = "L", + Short = "S", +} + +/** + * @public + */ +export enum OrderTypes { + LimitBuy = "Limit Buy Order", + LimitSell = "Limit Sell Order", + StopBuy = "Stop Buy Order", + StopSell = "Stop Sell Order", +} + /** * Value in map of {@link StockOrder} * @public @@ -290,17 +282,18 @@ export interface StockOrderObject { /** Price per share */ price: number; /** Order type */ - type: string; + type: OrderTypes; /** Order position */ - position: string; + position: PositionTypes; } /** * Return value of {@link TIX.getOrders | getOrders} + * + * Keys are stock symbols, properties are arrays of {@link StockOrderObject} * @public */ export interface StockOrder { - /** Stock Symbol */ [key: string]: StockOrderObject[]; } @@ -488,6 +481,8 @@ export interface BitNodeMultipliers { FourSigmaMarketDataApiCost: number; /** Influences how much it costs to unlock the stock market's 4S Market Data (NOT API) */ FourSigmaMarketDataCost: number; + /** Influences the respect gain and money gain of your gang. */ + GangSoftcap: number; /** Influences the experienced gained when hacking a server. */ HackExpGain: number; /** Influences how quickly the player's hacking level (not experience) scales */ @@ -508,10 +503,14 @@ export interface BitNodeMultipliers { PurchasedServerLimit: number; /** Influences the maximum allowed RAM for a purchased server */ PurchasedServerMaxRam: number; + /** Influences cost of any purchased server at or above 128GB */ + PurchasedServerSoftCap: number; /** Influences the minimum favor the player must have with a faction before they can donate to gain rep. */ RepToDonateToFaction: number; - /** Influences how much money can be stolen from a server when a script performs a hack against it. */ + /** Influences how much the money on a server can be reduced when a script performs a hack against it. */ ScriptHackMoney: number; + /** Influences how much of the money stolen by a scripted hack will be added to the player's money. */ + ScriptHackMoneyGain: number; /** Influences the growth percentage per cycle against a server. */ ServerGrowthRate: number; /** Influences the maxmimum money that a server can grow to. */ @@ -524,6 +523,12 @@ export interface BitNodeMultipliers { ServerWeakenRate: number; /** Influences how quickly the player's strength level (not exp) scales */ StrengthLevelMultiplier: number; + /** Influences the power of the gift */ + StaneksGiftPowerMultiplier: number; + /** Influences the size of the gift */ + StaneksGiftExtraSize: number; + /** Influences the hacking skill required to backdoor the world daemon. */ + WorldDaemonDifficulty: number; } /** @@ -535,9 +540,9 @@ export interface NodeStats { name: string; /** Node's level */ level: number; - /** Node's RAM */ + /** Node's RAM (GB) */ ram: number; - /** Node's used RAM */ + /** Node's used RAM (GB) */ ramUsed: number; /** Node's number of cores */ cores: number; @@ -956,6 +961,78 @@ export interface SleeveTask { factionWorkType: string; } +/** + * Object representing a port. A port is a serialized queue. + * @public + */ +export interface NetscriptPort { + /** + * Write data to a port. + * @remarks + * RAM cost: 0 GB + * + * @returns The data popped off the queue if it was full. + */ + write(value: string|number): null|string|number; + + /** + * Attempt to write data to the port. + * @remarks + * RAM cost: 0 GB + * + * @returns True if the data was added to the port, false if the port was full + */ + tryWrite(value: string|number): boolean; + + /** + * Shift an element out of the port. + * @remarks + * RAM cost: 0 GB + * + * This function will remove the first element from the port and return it. + * If the port is empty, then the string “NULL PORT DATA” will be returned. + * @returns the data read. + */ + read(): string|number; + + /** + * Retrieve the first element from the port without removing it. + * @remarks + * RAM cost: 0 GB + * + * This function is used to peek at the data from a port. It returns the + * first element in the specified port without removing that element. If + * the port is empty, the string “NULL PORT DATA” will be returned. + * @returns the data read + */ + peek(): string|number; + + /** + * Check if the port is full. + * @remarks + * RAM cost: 0 GB + * + * @returns true if the port is full, otherwise false + */ + full(): boolean; + + /** + * Check if the port is empty. + * @remarks + * RAM cost: 0 GB + * + * @returns true if the port is empty, otherwise false + */ + empty(): boolean; + + /** + * Empties all data from the port. + * @remarks + * RAM cost: 0 GB + */ + clear(): void; +} + /** * Stock market API * @public @@ -1213,6 +1290,8 @@ export interface TIX { * @remarks * RAM cost: 2.5 GB * This is an object containing information for all the Limit and Stop Orders you have in the stock market. + * For each symbol you have a position in, the returned object will have a key with that symbol's name. + * The object's properties are each an array of {@link StockOrderObject} * The object has the following structure: * * ```ts @@ -4440,11 +4519,11 @@ export interface NS extends Singularity { * //Get logs from foo.script on the foodnstuff server that was run with the arguments [1, "test"] * ns.tail("foo.script", "foodnstuff", 1, "test"); * ``` - * @param fn - Optional. Filename of the script being tailed. If omitted, the current script is tailed. + * @param fn - Optional. Filename or PID of the script being tailed. If omitted, the current script is tailed. * @param host - Optional. Hostname of the script being tailed. Defaults to the server this script is running on. If args are specified, this is not optional. * @param args - Arguments for the script being tailed. */ - tail(fn?: string, host?: string, ...args: any[]): void; + tail(fn?: FilenameOrPID, host?: string, ...args: any[]): void; /** * Get the list of servers connected to a server. @@ -5094,7 +5173,7 @@ export interface NS extends Singularity { * const [totalRam, ramUsed] = ns.getServerRam("helios"); * ``` * @param host - Host of target server. - * @returns Array with total and used memory on the specified server. + * @returns Array with total and used memory on the specified server, in GB. */ getServerRam(host: string): [number, number]; @@ -5104,7 +5183,7 @@ export interface NS extends Singularity { * RAM cost: 0.05 GB * * @param host - Hostname of the target server. - * @returns max ram + * @returns max ram (GB) */ getServerMaxRam(host: string): number; /** @@ -5113,7 +5192,7 @@ export interface NS extends Singularity { * RAM cost: 0.05 GB * * @param host - Hostname of the target server. - * @returns used ram + * @returns used ram (GB) */ getServerUsedRam(host: string): number; @@ -5187,6 +5266,7 @@ export interface NS extends Singularity { * RAM cost: 0.1 GB * * Returns a boolean indicating whether the specified script is running on the target server. + * If you use a PID instead of a filename, the hostname and args parameters are unnecessary. * Remember that a script is uniquely identified by both its name and its arguments. * * @example @@ -5213,12 +5293,12 @@ export interface NS extends Singularity { * //The function call will return true if there is a script named foo.script running with the arguments 1, 5, and “test” (in that order) on the joesguns server, and false otherwise: * ns.isRunning("foo.script", "joesguns", 1, 5, "test"); * ``` - * @param script - Filename of script to check. This is case-sensitive. + * @param script - Filename or PID of script to check. This is case-sensitive. * @param host - Host of target server. * @param args - Arguments to specify/identify which scripts to search for. * @returns True if specified script is running on the target server, and false otherwise. */ - isRunning(script: string, host: string, ...args: string[]): boolean; + isRunning(script: FilenameOrPID, host: string, ...args: string[]): boolean; /** * Get general info about a running script. @@ -5226,10 +5306,14 @@ export interface NS extends Singularity { * RAM cost: 0.3 GB * * Running with no args returns curent script. + * If you use a PID as the first parameter, the hostname and args parameters are unnecessary. * + * @param filename - Optional. Filename or PID of the script. + * @param hostname - Optional. Name of host server the script is running on. + * @param args - Arguments to identify the script * @returns info about a running script */ - getRunningScript(filename?: string | number, hostname?: string, ...args: (string | number)[]): RunningScript; + getRunningScript(filename?: FilenameOrPID, hostname?: string, ...args: (string | number)[]): RunningScript; /** * Get cost of purchasing a server. @@ -5252,7 +5336,7 @@ export interface NS extends Singularity { * ns.tprint(i + " -- " + ns.getPurchasedServerCost(Math.pow(2, i))); * } * ``` - * @param ram - Amount of RAM of a potential purchased server. Must be a power of 2 (2, 4, 8, 16, etc.). Maximum value of 1048576 (2^20). + * @param ram - Amount of RAM of a potential purchased server, in GB. Must be a power of 2 (2, 4, 8, 16, etc.). Maximum value of 1048576 (2^20). * @returns The cost to purchase a server with the specified amount of ram. */ getPurchasedServerCost(ram: number): number; @@ -5300,7 +5384,7 @@ export interface NS extends Singularity { * } * ``` * @param hostname - Host of the purchased server. - * @param ram - Amount of RAM of the purchased server. Must be a power of 2 (2, 4, 8, 16, etc.). Maximum value of 1048576 (2^20). + * @param ram - Amount of RAM of the purchased server, in GB. Must be a power of 2 (2, 4, 8, 16, etc.). Maximum value of 1048576 (2^20). * @returns The hostname of the newly purchased server. */ purchaseServer(hostname: string, ram: number): string; @@ -5341,7 +5425,7 @@ export interface NS extends Singularity { * Returns the maximum RAM that a purchased server can have. * * @remarks RAM cost: 0.05 GB - * @returns Returns the maximum RAM that a purchased server can have. + * @returns Returns the maximum RAM (in GB) that a purchased server can have. */ getPurchasedServerMaxRam(): number; @@ -5350,7 +5434,7 @@ export interface NS extends Singularity { * @remarks * RAM cost: 0 GB * - * This function can be used to either write data to a text file (.txt). + * This function can be used to write data to a text file (.txt). * * This function will write data to that text file. If the specified text file does not exist, * then it will be created. The third argument mode, defines how the data will be written to @@ -5359,7 +5443,7 @@ export interface NS extends Singularity { * then the data will be written in “append” mode which means that the data will be added at the * end of the text file. * - * @param handle - Port or text file that will be written to. + * @param handle - Filename of the text file that will be written to. * @param data - Data to write. * @param mode - Defines the write mode. Only valid when writing to text files. */ @@ -5385,13 +5469,13 @@ export interface NS extends Singularity { * @remarks * RAM cost: 0 GB * - * This function is used to read data from a port or from a text file (.txt). + * This function is used to read data from a text file (.txt). * * This function will return the data in the specified text * file. If the text file does not exist, an empty string will be returned. * - * @param handle - Port or text file to read from. - * @returns Data in the specified text file or port. + * @param handle - Filename to read from. + * @returns Data in the specified text file. */ read(handle: string): any; @@ -5463,9 +5547,8 @@ export interface NS extends Singularity { * * @see https://bitburner.readthedocs.io/en/latest/netscript/netscriptmisc.html#netscript-ports * @param port - Port number. Must be an integer between 1 and 20. - * @returns Data in the specified port. */ - getPortHandle(port: number): IPort; + getPortHandle(port: number): NetscriptPort; /** * Delete a file. @@ -5548,7 +5631,7 @@ export interface NS extends Singularity { * * @param script - Filename of script. This is case-sensitive. * @param host - Host of target server the script is located on. This is optional, If it is not specified then the function will se the current server as the target server. - * @returns Amount of RAM required to run the specified script on the target server, and 0 if the script does not exist. + * @returns Amount of RAM (in GB) required to run the specified script on the target server, and 0 if the script does not exist. */ getScriptRam(script: string, host?: string): number; diff --git a/src/Terminal/commands/ls.tsx b/src/Terminal/commands/ls.tsx index 5cc498308..2c00e7f28 100644 --- a/src/Terminal/commands/ls.tsx +++ b/src/Terminal/commands/ls.tsx @@ -1,9 +1,13 @@ +import { Theme } from "@mui/material/styles"; +import createStyles from "@mui/styles/createStyles"; +import makeStyles from "@mui/styles/makeStyles"; +import { toString } from "lodash"; import React from "react"; -import { ITerminal } from "../ITerminal"; -import { IRouter } from "../../ui/Router"; import { IPlayer } from "../../PersonObjects/IPlayer"; import { BaseServer } from "../../Server/BaseServer"; -import { getFirstParentDirectory, isValidDirectoryPath, evaluateDirectoryPath } from "../../Terminal/DirectoryHelpers"; +import { evaluateDirectoryPath, getFirstParentDirectory, isValidDirectoryPath } from "../../Terminal/DirectoryHelpers"; +import { IRouter } from "../../ui/Router"; +import { ITerminal } from "../ITerminal"; export function ls( terminal: ITerminal, @@ -113,7 +117,55 @@ export function ls( allMessages.sort(); folders.sort(); - function postSegments(segments: string[], style?: any): void { + interface ClickableScriptRowProps { + row: string; + prefix: string; + hostname: string; + } + + function ClickableScriptRow({ row, prefix, hostname }: ClickableScriptRowProps): React.ReactElement { + const classes = makeStyles((theme: Theme) => + createStyles({ + scriptLinksWrap: { + display: "flex", + color: theme.palette.warning.main, + }, + scriptLink: { + cursor: "pointer", + textDecorationLine: "underline", + paddingRight: "1.15em", + "&:last-child": { padding: 0 }, + }, + }), + )(); + + const rowSplit = row + .split(" ") + .map((x) => x.trim()) + .filter((x) => !!x); + + function onScriptLinkClick(filename: string): void { + if (player.getCurrentServer().hostname !== hostname) { + return terminal.error(`File is not on this server, connect to ${hostname} and try again`); + } + if (filename.startsWith("/")) filename = filename.slice(1); + const filepath = terminal.getFilepath(`${prefix}${filename}`); + const code = toString(terminal.getScript(player, filepath)?.code); + router.toScriptEditor({ [filepath]: code }); + } + + return ( + + {rowSplit.map((rowItem) => ( + onScriptLinkClick(rowItem)}> + {rowItem} + + ))} + + ); + } + + function postSegments(segments: string[], style?: any, linked?: boolean): void { const maxLength = Math.max(...segments.map((s) => s.length)) + 1; const filesPerRow = Math.floor(80 / maxLength); for (let i = 0; i < segments.length; i++) { @@ -128,7 +180,11 @@ export function ls( if (!style) { terminal.print(row); } else { - terminal.printRaw({row}); + if (linked) { + terminal.printRaw(); + } else { + terminal.printRaw({row}); + } } } } @@ -139,9 +195,9 @@ export function ls( { segments: allTextFiles }, { segments: allPrograms }, { segments: allContracts }, - { segments: allScripts, style: { color: "yellow", fontStyle: "bold" } }, + { segments: allScripts, style: { color: "yellow", fontStyle: "bold" }, linked: true }, ].filter((g) => g.segments.length > 0); for (let i = 0; i < groups.length; i++) { - postSegments(groups[i].segments, groups[i].style); + postSegments(groups[i].segments, groups[i].style, groups[i].linked); } } diff --git a/src/data/codingcontracttypes.ts b/src/data/codingcontracttypes.ts index b6645ee47..2b68dba1c 100644 --- a/src/data/codingcontracttypes.ts +++ b/src/data/codingcontracttypes.ts @@ -279,7 +279,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [ "i to i+n.", "\n\nAssuming you are initially positioned", "at the start of the array, determine whether you are", - "able to reach the last index exactly.\n\n", + "able to reach the last index.\n\n", "Your answer should be submitted as 1 or 0, representing true and false respectively", ].join(" "); }, diff --git a/src/utils/JSONReviver.ts b/src/utils/JSONReviver.ts index eeb832cec..5b7447d09 100644 --- a/src/utils/JSONReviver.ts +++ b/src/utils/JSONReviver.ts @@ -1,5 +1,7 @@ /* Generic Reviver, toJSON, and fromJSON functions used for saving and loading objects */ +import { validateObject } from "./Validator"; + interface IReviverValue { ctor: string; data: any; @@ -26,7 +28,11 @@ export function Reviver(key: string, value: IReviverValue | null): any { const ctor = Reviver.constructors[value.ctor]; if (typeof ctor === "function" && typeof ctor.fromJSON === "function") { - return ctor.fromJSON(value); + const obj = ctor.fromJSON(value); + if (ctor.validationData !== undefined) { + validateObject(obj, ctor.validationData); + } + return obj; } } return value; diff --git a/src/utils/Validator.ts b/src/utils/Validator.ts new file mode 100644 index 000000000..21daba59e --- /dev/null +++ b/src/utils/Validator.ts @@ -0,0 +1,78 @@ +export type ObjectValidator = { + [key in keyof T]?: ParameterValidator; +} + +interface ParameterValidatorObject { + default?: any; + min?: number; + max?: number; + func?: (obj: Type, validator: ObjectValidator, key: Key) => void; +} +type ParameterValidatorFunction = (obj: Type, key: Key) => void; +type ParameterValidator = ParameterValidatorObject | ParameterValidatorFunction + +export function validateObject, Key extends keyof Type>(obj: Type, validator: ObjectValidator): void { + for (const key of Object.keys(validator) as Key[]) { + const paramValidator = validator[key]; + if (paramValidator !== undefined) { + if (typeof paramValidator === 'function') { + paramValidator(obj, key); + } else { + if (paramValidator.func !== undefined) { + paramValidator.func(obj, validator, key); + } else { + if ((typeof obj[key]) !== (typeof paramValidator.default)) { + obj[key] = paramValidator.default + } + if (typeof obj[key] === 'number' && paramValidator.min !== undefined) { + if (obj[key] < paramValidator.min) obj[key] = paramValidator.min as Type[Key]; + } + if (typeof obj[key] === 'number' && paramValidator.max !== undefined) { + if (obj[key] > paramValidator.max) obj[key] = paramValidator.max as Type[Key]; + } + } + } + } + } +} + +export function minMax(def: number, min: number, max: number): (obj: Type, key: Key & keyof Type) => void { + return (obj, key) => { + if (typeof obj[key] !== 'number') { + obj[key] = def as unknown as Type[Key]; + return; + } + if ((obj[key] as unknown as number) < min) { + obj[key] = min as unknown as Type[Key]; + } + if ((obj[key] as unknown as number) > max) { + obj[key] = max as unknown as Type[Key]; + } + }; +} + +export function oneOf(def: Value, options: Value[]): (obj: Type, key: Key & keyof Type) => void { + return (obj, key) => { + if (typeof obj[key] !== typeof def) { + obj[key] = def as unknown as Type[Key]; + return; + } + if (!options.includes(obj[key] as unknown as Value)) { + obj[key] = def as unknown as Type[Key]; + } + }; +} + +export function subsetOf(options: Value[]): (obj: Type, key: Key & keyof Type) => void { + return (obj, key) => { + if (typeof obj[key] !== 'object' || !Array.isArray(obj[key])) { + obj[key] = [] as unknown as Type[Key]; + return; + } + const validValues: Value[] = []; + for (const value of obj[key] as unknown as Value[]) { + if (options.includes(value)) validValues.push(value); + } + obj[key] = validValues as unknown as Type[Key]; + }; +} \ No newline at end of file