// Defines a "Research Tree"
// Each Industry has a unique Research Tree
// Each Node in the Research Trees only holds the name(s) of Research,
// not an actual Research object. The name can be used to obtain a reference
// to the corresponding Research object using the ResearchMap
import { ResearchMap } from "./ResearchMap";
interface IConstructorParams {
children?: Node[];
cost: number;
text: string;
parent?: Node | null;
}
export class Node {
// All child Nodes in the tree
// The Research held in this Node is a prerequisite for all Research in
// child Nodes
children: Node[] = [];
// How much Scientific Research is needed for this
// Necessary to show it on the UI
cost: number = 0;
// Whether or not this Research has been unlocked
researched: boolean = false;
// Parent node in the tree
// The parent node defines the prerequisite Research (there can only be one)
// Set as null for no prerequisites
parent: Node | null = null;
// Name of the Research held in this Node
text: string = "";
constructor(p: IConstructorParams) {
if (ResearchMap[p.text] == null) {
throw new Error(`Invalid Research name used when constructing ResearchTree Node: ${p.text}`);
}
this.text = p.text;
this.cost = p.cost;
if (p.children && p.children.length > 0) {
this.children = p.children;
}
if (p.parent != null) {
this.parent = p.parent;
}
}
addChild(n: Node) {
this.children.push(n);
n.parent = this;
}
// Return an object that describes a TreantJS-compatible markup/config for this Node
// See: http://fperucic.github.io/treant-js/
createTreantMarkup(): object {
const childrenArray = [];
for (let i = 0; i < this.children.length; ++i) {
childrenArray.push(this.children[i].createTreantMarkup());
}
// Determine what css class this Node should have in the diagram
let htmlClass: string = "";
if (this.researched) {
htmlClass = "researched";
} else if (this.parent && this.parent.researched === false) {
htmlClass = "locked";
} else {
htmlClass = "unlocked";
}
const sanitizedName: string = this.text.replace(/\s/g, '');
return {
children: childrenArray,
HTMLclass: htmlClass,
innerHTML: `
${this.text}
${this.cost} Scientific Research
`,
text: { name: this.text },
}
}
// Recursive function for finding a Node with the specified text
findNode(text: string): Node | null {
// Is this the Node?
if (this.text === text) { return this; }
// Recursively search chilren
let res = null;
for (let i = 0; i < this.children.length; ++i) {
res = this.children[i].findNode(text);
if (res != null) { return res; }
}
return null;
}
setParent(n: Node) {
this.parent = n;
}
}
// A ResearchTree defines all available Research in an Industry
// The root node in a Research Tree must always be the "Hi-Tech R&D Laboratory"
export class ResearchTree {
root: Node | null = null;
constructor() {}
// Return an object that contains a Tree markup for TreantJS (using the JSON approach)
// See: http://fperucic.github.io/treant-js/
createTreantMarkup(): object {
if (this.root == null) { return {}; }
const treeMarkup = this.root.createTreantMarkup();
return {
chart: {
container: "",
},
nodeStructure: treeMarkup,
};
}
// Gets an array with the 'text' values of ALL Nodes in the Research Tree
getAllNodes(): string[] {
const res: string[] = [];
const queue: Node[] = [];
if (this.root == null) { return res; }
queue.push(this.root);
while (queue.length !== 0) {
const node: Node | undefined = queue.shift();
if (node == null) { continue; }
res.push(node.text);
for (let i = 0; i < node.children.length; ++i) {
queue.push(node.children[i]);
}
}
return res;
}
// Search for a Node with the given name ('text' property on the Node)
// Returns 'null' if it cannot be found
findNode(name: string): Node | null {
if (this.root == null) { return null; }
return this.root.findNode(name);
}
// Marks a Node as researched
research(name: string): void {
if (this.root == null) { return; }
const queue: Node[] = [];
queue.push(this.root);
while (queue.length !== 0) {
const node: Node | undefined = queue.shift();
if (node == null) { continue; }
if (node.text === name) {
node.researched = true;
}
for (let i = 0; i < node.children.length; ++i) {
queue.push(node.children[i]);
}
}
}
// Set the tree's Root Node
setRoot(root: Node): void {
this.root = root;
}
}