OpenWorldVGDL/javascript/core/core.js

397 lines
9.9 KiB
JavaScript
Raw Permalink Normal View History

2025-01-17 13:42:05 +00:00
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];
};
}