Make tutorial explain ns1 vs ns2 better

This commit is contained in:
Olivier Gagnon 2022-04-29 17:54:59 -04:00
parent ee4201c957
commit f10de908d4
6 changed files with 209 additions and 42 deletions

@ -7,6 +7,7 @@ import { ITutorialEvents } from "./ui/InteractiveTutorial/ITutorialEvents";
// Ordered array of keys to Interactive Tutorial Steps
enum iTutorialSteps {
Start,
NSSelection,
GoToCharacterPage, // Click on 'Stats' page
CharacterPage, // Introduction to 'Stats' page
CharacterGoToTerminalPage, // Go back to Terminal
@ -43,6 +44,7 @@ const ITutorial: {
isRunning: boolean;
stepIsDone: {
[iTutorialSteps.Start]: boolean;
[iTutorialSteps.NSSelection]: boolean;
[iTutorialSteps.GoToCharacterPage]: boolean;
[iTutorialSteps.CharacterPage]: boolean;
[iTutorialSteps.CharacterGoToTerminalPage]: boolean;
@ -80,6 +82,7 @@ const ITutorial: {
// Keeps track of whether each step has been done
stepIsDone: {
[iTutorialSteps.Start]: false,
[iTutorialSteps.NSSelection]: false,
[iTutorialSteps.GoToCharacterPage]: false,
[iTutorialSteps.CharacterPage]: false,
[iTutorialSteps.CharacterGoToTerminalPage]: false,

@ -544,11 +544,14 @@ export function Root(props: IProps): React.ReactElement {
// this is duplicate code with saving later.
if (ITutorial.isRunning && ITutorial.currStep === iTutorialSteps.TerminalTypeScript) {
//Make sure filename + code properly follow tutorial
if (currentScript.fileName !== "n00dles.script") {
dialogBoxCreate("Leave the script name as 'n00dles.script'!");
if (currentScript.fileName !== "n00dles.script" && currentScript.fileName !== "n00dles.js") {
dialogBoxCreate("Don't change the script name for now.");
return;
}
if (currentScript.code.replace(/\s/g, "").indexOf("while(true){hack('n00dles');}") == -1) {
const cleanCode = currentScript.code.replace(/\s/g, "");
const ns1 = "while(true){hack('n00dles');}";
const ns2 = `exportasyncfunctionmain(ns){while(true){awaitns.hack('n00dles');}}`;
if (cleanCode.indexOf(ns1) == -1 && cleanCode.indexOf(ns2) == -1) {
dialogBoxCreate("Please copy and paste the code from the tutorial!");
return;
}

@ -718,7 +718,11 @@ export class Terminal implements ITerminal {
}
break;
case iTutorialSteps.TerminalCreateScript:
if (commandArray.length == 2 && commandArray[0] == "nano" && commandArray[1] == "n00dles.script") {
if (
commandArray.length == 2 &&
commandArray[0] == "nano" &&
(commandArray[1] == "n00dles.script" || commandArray[1] == "n00dles.js")
) {
iTutorialNextStep();
} else {
this.error("Bad command. Please follow the tutorial");
@ -734,7 +738,11 @@ export class Terminal implements ITerminal {
}
break;
case iTutorialSteps.TerminalRunScript:
if (commandArray.length == 2 && commandArray[0] == "run" && commandArray[1] == "n00dles.script") {
if (
commandArray.length == 2 &&
commandArray[0] == "run" &&
(commandArray[1] == "n00dles.script" || commandArray[1] == "n00dles.js")
) {
iTutorialNextStep();
} else {
this.error("Bad command. Please follow the tutorial");
@ -742,7 +750,11 @@ export class Terminal implements ITerminal {
}
break;
case iTutorialSteps.ActiveScriptsToTerminal:
if (commandArray.length == 2 && commandArray[0] == "tail" && commandArray[1] == "n00dles.script") {
if (
commandArray.length == 2 &&
commandArray[0] == "tail" &&
(commandArray[1] == "n00dles.script" || commandArray[1] == "n00dles.js")
) {
iTutorialNextStep();
} else {
this.error("Bad command. Please follow the tutorial");

@ -9,6 +9,7 @@ import ArrowBackIos from "@mui/icons-material/ArrowBackIos";
import { ITutorialEvents } from "./ITutorialEvents";
import { CopyableText } from "../React/CopyableText";
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import EqualizerIcon from "@mui/icons-material/Equalizer";
import LastPageIcon from "@mui/icons-material/LastPage";
@ -27,6 +28,7 @@ import {
iTutorialSteps,
iTutorialEnd,
} from "../../InteractiveTutorial";
import { NSSelection } from "./NSSelection";
interface IContent {
content: React.ReactElement;
@ -45,9 +47,23 @@ const useStyles = makeStyles((theme: Theme) =>
}),
);
enum Language {
None,
NS1,
NS2,
}
export function InteractiveTutorialRoot(): React.ReactElement {
const [nsSelectionOpen, setNSSelectionOpen] = useState(false);
const [language, setLanguage] = useState(Language.None);
const classes = useStyles();
const tutorialScriptName = {
[Language.None]: "n00dles.script",
[Language.NS1]: "n00dles.script",
[Language.NS2]: "n00dles.js",
}[language];
const contents: { [number: string]: IContent | undefined } = {
[iTutorialSteps.Start as number]: {
content: (
@ -66,6 +82,47 @@ export function InteractiveTutorialRoot(): React.ReactElement {
),
canNext: true,
},
[iTutorialSteps.NSSelection as number]: {
content: (
<>
<Typography>The tutorial will adjust to your programming ability.</Typography>
<Typography>Bitburner has 2 types of scripts:</Typography>
<List>
<ListItem>
<Typography>NS1: Javascript from 2009, very simple. Recommended for beginner to programming.</Typography>
</ListItem>
<ListItem>
<Typography>
NS2: Native, modern Javascript. Recommended if you know any programming language or are serious about
learning programming.
</Typography>
</ListItem>
</List>
<Typography>
Both are available at all time and interchangeably. This choice is only for the tutorial.
</Typography>
<Button
onClick={() => {
setLanguage(Language.NS1);
iTutorialNextStep();
}}
>
Use NS1
</Button>
<Button
onClick={() => {
setLanguage(Language.NS2);
iTutorialNextStep();
}}
>
Use NS2
</Button>
<Button onClick={() => setNSSelectionOpen(true)}>More info</Button>
<br />
</>
),
canNext: false,
},
[iTutorialSteps.GoToCharacterPage as number]: {
content: (
<>
@ -321,7 +378,7 @@ export function InteractiveTutorialRoot(): React.ReactElement {
<Typography classes={{ root: classes.textfield }}>{"[home ~/]> nano"}</Typography>
<Typography>Scripts must end with the .script extension. Let's make a script now by entering </Typography>
<Typography classes={{ root: classes.textfield }}>{"[home ~/]> nano n00dles.script"}</Typography>
<Typography classes={{ root: classes.textfield }}>{`[home ~/]> nano ${tutorialScriptName}`}</Typography>
<Typography>
after the hack command finishes running (Sidenote: Pressing ctrl + c will end a command like hack early)
@ -334,16 +391,28 @@ export function InteractiveTutorialRoot(): React.ReactElement {
content: (
<>
<Typography>
This is the script editor. You can use it to program your scripts. Scripts are written in a simplified
version of javascript. Copy and paste the following code into the script editor: <br />
This is the script editor. You can use it to program your scripts.{" "}
{language !== Language.NS2 && <>Scripts are written in a simplified version of javascript.</>} Copy and
paste the following code into the script editor: <br />
</Typography>
<Typography classes={{ root: classes.code }}>
<CopyableText
value={`while(true) {
{language !== Language.NS2 && (
<CopyableText
value={`while(true) {
hack('n00dles');
}`}
/>
/>
)}
{language === Language.NS2 && (
<CopyableText
value={`export async function main(ns) {
while(true) {
await ns.hack('n00dles');
}
}`}
/>
)}
</Typography>
<Typography>
For anyone with basic programming experience, this code should be straightforward. This script will
@ -378,7 +447,7 @@ export function InteractiveTutorialRoot(): React.ReactElement {
<Typography>
We have 8GB of free RAM on this machine, which is enough to run our script. Let's run our script using
</Typography>
<Typography classes={{ root: classes.textfield }}>{"[home ~/]> run n00dles.script"}</Typography>
<Typography classes={{ root: classes.textfield }}>{`[home ~/]> run ${tutorialScriptName}`}</Typography>
</>
),
canNext: false,
@ -425,7 +494,7 @@ export function InteractiveTutorialRoot(): React.ReactElement {
One last thing about scripts, each active script contains logs that detail what it's doing. We can check
these logs using the tail command. Do that now for the script we just ran by typing{" "}
</Typography>
<Typography classes={{ root: classes.textfield }}>{"[home ~/]> tail n00dles.script"}</Typography>
<Typography classes={{ root: classes.textfield }}>{`[home ~/]> tail ${tutorialScriptName}`}</Typography>
</>
),
canNext: false,
@ -447,14 +516,6 @@ export function InteractiveTutorialRoot(): React.ReactElement {
in the main navigation menu to look at the documentation.
<br />
<br />
If you know even a little bit of programming it is highly recommended you use NS2 instead. You will enjoy
the game much more. NS1 files end with .script and are a subset of javascript. NS2 files end with .js and
are full speed native javascript.
<br />
<br />
You can learn more about the difference between them later in the documentation.
<br />
<br />
For now, let's move on to something else!
</Typography>
</>
@ -549,25 +610,30 @@ export function InteractiveTutorialRoot(): React.ReactElement {
const content = contents[step];
if (content === undefined) throw new Error("error in the tutorial");
return (
<Paper square sx={{ maxWidth: "70vw", p: 2 }}>
{content.content}
{step !== iTutorialSteps.TutorialPageInfo && (
<>
<IconButton onClick={iTutorialPrevStep} aria-label="previous">
<ArrowBackIos />
</IconButton>
{content.canNext && (
<IconButton onClick={iTutorialNextStep} aria-label="next">
<ArrowForwardIos />
</IconButton>
)}
</>
)}
<br />
<br />
<Button onClick={iTutorialEnd}>
{step !== iTutorialSteps.TutorialPageInfo ? "SKIP TUTORIAL" : "FINISH TUTORIAL"}
</Button>
</Paper>
<>
<NSSelection open={nsSelectionOpen} onClose={() => setNSSelectionOpen(false)} />
<Paper square sx={{ maxWidth: "70vw", p: 2 }}>
{content.content}
{step !== iTutorialSteps.TutorialPageInfo && (
<>
{step !== iTutorialSteps.Start && (
<IconButton onClick={iTutorialPrevStep} aria-label="previous">
<ArrowBackIos />
</IconButton>
)}
{content.canNext && (
<IconButton onClick={iTutorialNextStep} aria-label="next">
<ArrowForwardIos />
</IconButton>
)}
</>
)}
<br />
<br />
<Button onClick={iTutorialEnd}>
{step !== iTutorialSteps.TutorialPageInfo ? "SKIP TUTORIAL" : "FINISH TUTORIAL"}
</Button>
</Paper>
</>
);
}

@ -0,0 +1,82 @@
import Editor from "@monaco-editor/react";
import { Tab, Tabs, Typography } from "@mui/material";
import React from "react";
import { Modal } from "../React/Modal";
import * as monaco from "monaco-editor";
type IStandaloneCodeEditor = monaco.editor.IStandaloneCodeEditor;
interface IProps {
open: boolean;
onClose: () => void;
}
const ns1Example = `while(true) {
hack('n00dles');
}`;
const ns2Example = `/** @param {NS} ns */
export async function main(ns) {
while(true) {
await ns.hack('n00dles');
}
}`;
export function NSSelection(props: IProps): React.ReactElement {
const [value, setValue] = React.useState(0);
function handleChange(event: React.SyntheticEvent, tab: number): void {
setValue(tab);
}
function onMount(editor: IStandaloneCodeEditor): void {
editor.updateOptions({ readOnly: true });
}
return (
<Modal open={props.open} onClose={props.onClose}>
<Tabs variant="fullWidth" value={value} onChange={handleChange}>
<Tab label="NS1" />
<Tab label="NS2" />
</Tabs>
{value === 0 && (
<>
<Typography>
These scripts end with '.script'. Using a very old interpreted version of javascript. It is perfect for
beginner to programming.
</Typography>
<Typography>Example script using NS1:</Typography>
<Editor
loading={<></>}
defaultLanguage="javascript"
defaultValue={ns1Example}
height={`${300}px`}
theme={"vs-dark"}
onMount={onMount}
options={{ fontSize: 30 }}
/>
</>
)}
{value === 1 && (
<>
<Typography>
These scripts end with '.js'. Scripts using ns2 are running natively along the game. If you know any
programming language you should be using this. However if you're unfamiliar with javascript Promises you
might want to read up on them a little bit before diving in.
</Typography>
<Typography>Example script using NS2:</Typography>
<Editor
loading={<></>}
defaultLanguage="javascript"
defaultValue={ns2Example}
height={`${300}px`}
theme={"vs-dark"}
options={{ fontSize: 30 }}
/>
</>
)}
</Modal>
);
}

@ -12,6 +12,7 @@ const useStyles = makeStyles((theme: Theme) =>
display: "flex",
alignItems: "center",
justifyContent: "center",
zIndex: 999999,
},
paper: {
backgroundColor: theme.palette.background.default,