OpenWorldVGDL/javascript/core/core.js
2025-01-17 21:42:05 +08:00

397 lines
9.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import * as tools from "./tools.js";
import * as Resource from "./ontology/resource.js";
import * as Games from "./games.js";
import * as Sprite from "./ontology/vgdl-sprite.js";
import * as Constants from "./ontology/constants.js";
import * as Avatars from "./ontology/avatar.js";
import * as Termination from "./ontology/termination.js";
import * as Condition from "./ontology/conditional.js";
import * as Effect from "./ontology/effect.js";
import * as Physics from "./ontology/physics.js";
import { killSprite, scoreChange } from "./ontology/effect.js";
import { BasicGame } from "./games.js";
const EVALS = {
True: true,
False: false,
};
for (const x of [
tools,
Games,
Sprite,
Constants,
Avatars,
Termination,
Condition,
Effect,
Physics,
Resource,
]) {
const constantKeys = Object.keys(x);
// 遍历属性并添加到字典中
constantKeys.forEach((key) => {
EVALS[key] = x[key];
});
}
export class Node {
constructor(content, indent, parent = null, line = 0) {
this.children = [];
this.content = content;
this.indent = indent;
this.parent = null;
this.line = line;
if (parent) parent.insert(this);
}
insert = (node) => {
if (this.indent < node.indent) {
this.children.push(node);
node.parent = this;
} else {
this.parent.insert(node);
}
};
getRoot = () => {
if (this.parent) return this.parent.getRoot();
else return this;
};
}
function indentTreeParser(s = "", tabsize = 8) {
s = s.replace("\t", " ".repeat(tabsize));
s = s.replace("(", " ").replace(")", " ").replace(",", " ");
let lines = s.split("\n");
let last = new Node("", -1);
const root = last;
for (let i = 0; i < lines.length; i++) {
let line = lines[i];
if (line.includes("#")) {
line = line.split("#")[0];
}
const content = line.trim();
if (content.length > 0) {
const indent = line.length - line.trimStart().length;
last = new Node(content, indent, last, i);
}
}
// for (let line of lines) {
// if(line.includes("#")){
// line = line.split("#")[0]
// }
// const content = line.trim()
// if(content.length > 0){
// const indent = line.length - line.trimStart().length
// last = new Node(content, indent, last)
// }
// }
return root;
}
function _eval(estr) {
if (estr === "True") return true;
if (estr === "False") return false;
try {
return eval(estr);
} catch (e) {
if (!EVALS.hasOwnProperty(estr)) throw e;
return eval(`EVALS.${estr}`);
}
}
export class VGDLParser {
game = null;
images = ["error.png"];
ignore_colors = ["wall", "avatar"];
var_colors = {};
/**
*
* @param {string} tree
* @param {number} seed
* @returns {BasicGame}
*/
parseGame = (tree, seed = null) => {
if (!(tree instanceof Node)) tree = indentTreeParser(tree);
const root = tree.children[0];
const [sclass, args] = this.parseArgs(root.content);
this.game = new sclass(args);
// Parse Parameter Set first
for (const c of root.children) {
if (c.content === "ParameterSet") {
}
}
try {
for (const c of root.children) {
switch (c.content) {
case "SpriteSet":
this.parseSprites(c.children);
// this.parseSprites(c.children)
break;
case "InteractionSet":
this.parseInteraction(c.children);
break;
case "LevelMapping":
this.parseLevel(c.children);
break;
case "TerminationSet":
this.parseTermination(c.children);
break;
case "ConditionSet":
this.parseCondition(c.children);
break;
default:
console.error(`Unknown content: ${c.content} at line ${c.line}`);
break;
}
}
} catch (e) {
throw e;
}
const keys = [];
let colors = [];
for (const key in this.var_colors) {
keys.push(key);
colors.push(this.game.sprite_constr[key][1].color);
}
if (seed) {
colors = colors.shuffled(seed);
let index = 0;
colors.forEach((c) => {
this.game.sprite_constr[keys[index]][1].color = c;
index++;
});
}
this.game.images = this.images.slice();
return this.game;
};
parseLevel = (mnodes) => {
mnodes.forEach((mnode) => {
try {
if (mnode.content.indexOf(">") === -1) {
throw "No '>' symbol in level mapping";
}
const [c, val] = mnode.content.split(">").map((x) => {
return x.trim();
});
// console.assert(
// c.length === 1,
// "Only single character mappings allowed",
// );
const keys = val.split(" ").map((x) => {
return x.trim();
});
// console.debug("Mapping", c, keys);
this.game.char_mapping[c] = keys;
} catch (e) {
throw new Error(
`Parse Level Mapping Fail at Line ${mnode.line}: \n ${mnode.content} \n ${e.toString()}`,
);
}
});
};
parseCondition = (cnodes) => {
cnodes.forEach((c) => {
if (c.content.indexOf(">") !== -1) {
const [conditional, interaction] = c.content
.split(">")
.map((s) => s.trim());
try {
const [cclass, cargs] = this.parseArgs(conditional);
const [eclass, eargs] = this.parseArgs(conditional);
// console.debug(`Adding Condition ${conditional} ${interaction}`);
this.game.conditions.push([new cclass(cargs), [eclass, eargs]]);
} catch (e) {
throw new Error(
`Parse Condition Fail at Line ${c.line}: \n ${c.content} \n ${e.toString()}`,
);
}
}
});
};
parseTermination = (tnodes) => {
tnodes.forEach((t) => {
try {
const [sclass, args] = this.parseArgs(t.content);
// console.debug(`Adding Termination: ${sclass} ${args}`);
this.game.terminations.push(new sclass(args));
} catch (e) {
throw new Error(
`Parse Termination Fail at Line ${t.line}:\n ${t.content} \n ${e.toString()}`,
);
}
});
};
parseInteraction = (inodes) => {
inodes.forEach((i) => {
if (i.content.indexOf(">") !== -1) {
try {
const [pair, edef] = i.content.split(">").map((s) => s.trim());
const [eclass, args] = this.parseArgs(edef);
//支持object为多个的情况测试环境为surround
const pairs = pair
.split(" ")
.map((s) => s.trim())
.filter((s) => s);
const subject = pairs[0];
for (let pair_idx = 1; pair_idx < pairs.length; pair_idx++) {
const object = pairs[pair_idx];
this.game.collision_eff.push([subject, object, eclass, args]);
if (args["scoreChange"]) {
this.game.collision_eff.push([
subject,
object,
scoreChange,
{ score: args["scoreChange"] },
]);
}
if (args["killSecond"]) {
this.game.collision_eff.push([
object,
subject,
killSprite,
{ score: args["killSecond"] },
]);
}
}
// console.debug(`Adding Collision ${pair} has effect: ${edef}`);
} catch (e) {
throw new Error(
`Parse Interaction Fail at Line ${i.line}: \n ${i.content} \n ${e.toString()}`,
);
}
}
});
};
parseSprites = (
snodes,
parentClass = null,
parentargs = {},
parenttypes = [],
) => {
snodes.forEach((s) => {
try {
// console.assert(s.content.indexOf(">") !== -1);
const [key, sdef] = s.content.split(">").map((s) => s.trim());
let [sclass, args] = this.parseArgs(
sdef,
parentClass,
Object.assign({}, parentargs),
);
if ("image" in args) {
this.images.push(args.image);
}
const stypes = parenttypes.concat(key);
if ("singleton" in args) {
if (args["singleton"] === true) this.game.singletons.push(key);
args = JSON.parse(JSON.stringify(args));
delete args["singleton"];
}
if (s.children.length === 0) {
// console.debug(`Defining: ${key} ${sclass} ${args} ${stypes}`);
this.game.sprite_constr[key] = [sclass, args, stypes];
if (
args.color &&
!("color" in parentargs) &&
!this.ignore_colors.contains(key)
) {
this.var_colors[key] = args.color;
}
if (this.game.sprite_order.contains(key))
this.game.sprite_order.remove(key);
this.game.sprite_order.push(key);
let dict = this.game.objectTypes;
for (const parent of stypes) {
if (parent === key) {
break;
}
dict = dict[parent];
}
dict[key] = {};
} else {
let dict = this.game.objectTypes;
for (const parent of stypes) {
if (parent === key) {
break;
}
dict = dict[parent];
}
dict[key] = {};
this.parseSprites(s.children, sclass, args, stypes);
}
} catch (e) {
throw new Error(
`Parse Sprite Fail at Line ${s.line}: \n ${s.content} \n ${e.toString()}`,
);
}
});
};
parseArgs = (s, sclass = {}, args = {}) => {
let sparts = s
.split(" ")
.filter((c) => c.length > 0)
.map((c) => c.trim());
if (sparts.length === 0) return [sclass, args];
if (sparts[0].indexOf("=") === -1) {
sclass = _eval(sparts[0]);
sparts = sparts.slice(1);
}
sparts.forEach((spart) => {
const [k, val] = spart.split("=");
try {
args[k] = _eval(val);
} catch (e) {
args[k] = val;
}
});
return [sclass, args];
};
}