Merge pull request #154 from hppeng-wynn/atree-assassin
assassin ability tree implementation
This commit is contained in:
commit
90ce005485
5 changed files with 6938 additions and 1300 deletions
45
js/atree.js
45
js/atree.js
|
@ -41,8 +41,8 @@ add_spell_prop: {
|
||||||
// If target part does not exist, a new part is created.
|
// If target part does not exist, a new part is created.
|
||||||
behavior: Optional[str] // One of: "merge", "modify". default: merge
|
behavior: Optional[str] // One of: "merge", "modify". default: merge
|
||||||
// merge: add if exist, make new part if not exist
|
// merge: add if exist, make new part if not exist
|
||||||
// modify: change existing part. do nothing if not exist
|
// modify: increment existing part. do nothing if not exist
|
||||||
cost: Optional[int] // change to spellcost
|
cost: Optional[int] // change to spellcost. If the spell is not spell 1-4, this must be left empty.
|
||||||
multipliers: Optional[array[float, 6]] // Additive changes to spellmult (for damage spell)
|
multipliers: Optional[array[float, 6]] // Additive changes to spellmult (for damage spell)
|
||||||
power: Optional[float] // Additive change to healing power (for heal spell)
|
power: Optional[float] // Additive change to healing power (for heal spell)
|
||||||
hits: Optional[Map[str, float]] // Additive changes to hits (for total entry)
|
hits: Optional[Map[str, float]] // Additive changes to hits (for total entry)
|
||||||
|
@ -62,7 +62,7 @@ raw_stat: {
|
||||||
// string value means bind to (or create) named button
|
// string value means bind to (or create) named button
|
||||||
behavior: Optional[str] // One of: "merge", "modify". default: merge
|
behavior: Optional[str] // One of: "merge", "modify". default: merge
|
||||||
// merge: add if exist, make new part if not exist
|
// merge: add if exist, make new part if not exist
|
||||||
// modify: change existing part. do nothing if not exist
|
// modify: increment existing part. do nothing if not exist
|
||||||
bonuses: List[stat_bonus]
|
bonuses: List[stat_bonus]
|
||||||
}
|
}
|
||||||
stat_bonus: {
|
stat_bonus: {
|
||||||
|
@ -170,40 +170,23 @@ const atree_node = new (class extends ComputeNode {
|
||||||
}
|
}
|
||||||
node.parents = parents;
|
node.parents = parents;
|
||||||
}
|
}
|
||||||
console.log(atree_map);
|
|
||||||
|
|
||||||
|
let sccs = make_SCC_graph(atree_head, atree_map.values());
|
||||||
let atree_topo_sort = [];
|
let atree_topo_sort = [];
|
||||||
topological_sort_tree(atree_head, atree_topo_sort, new Map());
|
for (const scc of sccs) {
|
||||||
atree_topo_sort.reverse();
|
for (const node of scc.nodes) {
|
||||||
|
delete node.visited;
|
||||||
|
delete node.assigned;
|
||||||
|
delete node.scc;
|
||||||
|
atree_topo_sort.push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log("Approximate topological order ability tree:");
|
||||||
|
console.log(atree_topo_sort);
|
||||||
return atree_topo_sort;
|
return atree_topo_sort;
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a reverse topological sort of the tree in the result list.
|
|
||||||
* NOTE: our structure isn't a tree... it isn't even acyclic... but do it anyway i guess...
|
|
||||||
*
|
|
||||||
* https://en.wikipedia.org/wiki/Topological_sorting
|
|
||||||
* @param tree: Root of tree to sort
|
|
||||||
* @param res: Result list (reverse topological order)
|
|
||||||
* @param mark_state: Bookkeeping. Call with empty Map()
|
|
||||||
*/
|
|
||||||
function topological_sort_tree(tree, res, mark_state) {
|
|
||||||
const state = mark_state.get(tree);
|
|
||||||
if (state === undefined) {
|
|
||||||
// unmarked.
|
|
||||||
mark_state.set(tree, false); // temporary mark
|
|
||||||
for (const child of tree.children) {
|
|
||||||
topological_sort_tree(child, res, mark_state);
|
|
||||||
}
|
|
||||||
mark_state.set(tree, true); // permanent mark
|
|
||||||
res.push(tree);
|
|
||||||
}
|
|
||||||
// these cases are not needed. Case 1 does nothing, case 2 should never happen.
|
|
||||||
// else if (state === true) { return; } // permanent mark.
|
|
||||||
// else if (state === false) { throw "not a DAG"; } // temporary mark.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display ability tree from topologically sorted list.
|
* Display ability tree from topologically sorted list.
|
||||||
*
|
*
|
||||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
|
@ -223,21 +223,15 @@ function construct_scc_graph(items_to_consider) {
|
||||||
let terminal_node = {
|
let terminal_node = {
|
||||||
item: null,
|
item: null,
|
||||||
children: [],
|
children: [],
|
||||||
parents: nodes,
|
parents: nodes
|
||||||
visited: false,
|
|
||||||
assigned: false,
|
|
||||||
scc: null
|
|
||||||
};
|
};
|
||||||
let root_node = {
|
let root_node = {
|
||||||
item: null,
|
item: null,
|
||||||
children: nodes,
|
children: nodes,
|
||||||
parents: [],
|
parents: [],
|
||||||
visited: false,
|
|
||||||
assigned: false,
|
|
||||||
scc: null
|
|
||||||
};
|
};
|
||||||
for (const item of items_to_consider) {
|
for (const item of items_to_consider) {
|
||||||
nodes.push({item: item, children: [terminal_node], parents: [root_node], visited: false, assigned: false, scc: null});
|
nodes.push({item: item, children: [terminal_node], parents: [root_node]});
|
||||||
}
|
}
|
||||||
// Dependency graph construction.
|
// Dependency graph construction.
|
||||||
for (const node_a of nodes) {
|
for (const node_a of nodes) {
|
||||||
|
@ -253,50 +247,6 @@ function construct_scc_graph(items_to_consider) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const res = []
|
const sccs = make_SCC_graph(root_node, nodes);
|
||||||
/*
|
|
||||||
* SCC graph construction.
|
|
||||||
* https://en.wikipedia.org/wiki/Kosaraju%27s_algorithm
|
|
||||||
*/
|
|
||||||
function visit(u, res) {
|
|
||||||
if (u.visited) { return; }
|
|
||||||
u.visited = true;
|
|
||||||
for (const child of u.children) {
|
|
||||||
if (!child.visited) { visit(child, res); }
|
|
||||||
}
|
|
||||||
res.push(u);
|
|
||||||
}
|
|
||||||
visit(root_node, res);
|
|
||||||
res.reverse();
|
|
||||||
const sccs = [];
|
|
||||||
function assign(node, cur_scc) {
|
|
||||||
if (node.assigned) { return; }
|
|
||||||
cur_scc.nodes.push(node);
|
|
||||||
node.scc = cur_scc;
|
|
||||||
node.assigned = true;
|
|
||||||
for (const parent of node.parents) {
|
|
||||||
assign(parent, cur_scc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const node of res) {
|
|
||||||
if (node.assigned) { continue; }
|
|
||||||
const cur_scc = {
|
|
||||||
nodes: [],
|
|
||||||
children: new Set(),
|
|
||||||
parents: new Set()
|
|
||||||
};
|
|
||||||
assign(node, cur_scc);
|
|
||||||
sccs.push(cur_scc);
|
|
||||||
}
|
|
||||||
for (const scc of sccs) {
|
|
||||||
for (const node of scc.nodes) {
|
|
||||||
for (const child of node.children) {
|
|
||||||
scc.children.add(child.scc);
|
|
||||||
}
|
|
||||||
for (const parent of node.parents) {
|
|
||||||
scc.parents.add(parent.scc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [root_node, terminal_node, sccs];
|
return [root_node, terminal_node, sccs];
|
||||||
}
|
}
|
||||||
|
|
64
js/utils.js
64
js/utils.js
|
@ -889,3 +889,67 @@ function make_elem(type, classlist = [], args = {}) {
|
||||||
}
|
}
|
||||||
return ret_elem;
|
return ret_elem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nodes must have:
|
||||||
|
* node: {
|
||||||
|
* parents: List[node]
|
||||||
|
* children: List[node]
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* This function will define: "visited, assigned, scc" properties
|
||||||
|
* Assuming a connected graph. (only one root)
|
||||||
|
*/
|
||||||
|
function make_SCC_graph(root_node, nodes) {
|
||||||
|
for (const node of nodes) {
|
||||||
|
node.visited = false;
|
||||||
|
node.assigned = false;
|
||||||
|
node.scc = null;
|
||||||
|
}
|
||||||
|
const res = []
|
||||||
|
/*
|
||||||
|
* SCC graph construction.
|
||||||
|
* https://en.wikipedia.org/wiki/Kosaraju%27s_algorithm
|
||||||
|
*/
|
||||||
|
function visit(u, res) {
|
||||||
|
if (u.visited) { return; }
|
||||||
|
u.visited = true;
|
||||||
|
for (const child of u.children) {
|
||||||
|
if (!child.visited) { visit(child, res); }
|
||||||
|
}
|
||||||
|
res.push(u);
|
||||||
|
}
|
||||||
|
visit(root_node, res);
|
||||||
|
res.reverse();
|
||||||
|
const sccs = [];
|
||||||
|
function assign(node, cur_scc) {
|
||||||
|
if (node.assigned) { return; }
|
||||||
|
cur_scc.nodes.push(node);
|
||||||
|
node.scc = cur_scc;
|
||||||
|
node.assigned = true;
|
||||||
|
for (const parent of node.parents) {
|
||||||
|
assign(parent, cur_scc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const node of res) {
|
||||||
|
if (node.assigned) { continue; }
|
||||||
|
const cur_scc = {
|
||||||
|
nodes: [],
|
||||||
|
children: new Set(),
|
||||||
|
parents: new Set()
|
||||||
|
};
|
||||||
|
assign(node, cur_scc);
|
||||||
|
sccs.push(cur_scc);
|
||||||
|
}
|
||||||
|
for (const scc of sccs) {
|
||||||
|
for (const node of scc.nodes) {
|
||||||
|
for (const child of node.children) {
|
||||||
|
scc.children.add(child.scc);
|
||||||
|
}
|
||||||
|
for (const parent of node.parents) {
|
||||||
|
scc.parents.add(parent.scc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sccs;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue