826 lines
22 KiB
JavaScript
826 lines
22 KiB
JavaScript
import { defaultDict, includesArray, initializeDistribution } from "./tools.js";
|
|
import { colorDict, DARKGRAY, GOLD } from "./ontology/constants.js";
|
|
import { EOS, Immovable, VGDLSprite } from "./ontology/vgdl-sprite.js";
|
|
import { MovingAvatar } from "./ontology/avatar.js";
|
|
import { Termination } from "./ontology/termination.js";
|
|
import { scoreChange, stochastic_effects } from "./ontology/effect.js";
|
|
import { Resource } from "./ontology/resource";
|
|
import { ContinuousPhysics, distance, quickDistance } from "./ontology/physics.js";
|
|
|
|
const MAX_SPRITES = 10000;
|
|
|
|
export class BasicGame {
|
|
default_mapping = {
|
|
w: ["wall"],
|
|
A: ["avatar"],
|
|
};
|
|
|
|
block_size = 10;
|
|
load_save_enabled = true;
|
|
disableContinuousKeyPress = true;
|
|
image_dir = "../sprites";
|
|
|
|
args = {};
|
|
|
|
score = 0;
|
|
bonus_score = 0;
|
|
real_start_time = 0;
|
|
real_time = 0;
|
|
time = 0;
|
|
ended = false;
|
|
num_sprites = 0;
|
|
kill_list = [];
|
|
all_killed = [];
|
|
|
|
frame_rate = 20;
|
|
|
|
sprite_constr = {
|
|
wall: [Immovable, { color: DARKGRAY }, ["wall"]],
|
|
avatar: [MovingAvatar, {}, ["avatar"]],
|
|
};
|
|
|
|
// z-level of sprite types (in case of overlap)
|
|
sprite_order = ["wall", "avatar"];
|
|
|
|
// contains instance lists
|
|
/**
|
|
* @type {Object.<string, VGDLSprite[]>}
|
|
*/
|
|
sprite_groups = {};
|
|
// which sprite types (abstract or not) are singletons?
|
|
singletons = [];
|
|
// collision effects (ordered by execution order)
|
|
collision_eff = [];
|
|
collision_types = [];
|
|
|
|
playback_actions = [];
|
|
playbacx_index = 0;
|
|
// for reading levels
|
|
char_mapping = {};
|
|
// temination criteria
|
|
terminations = [new Termination()];
|
|
// conditional criteria
|
|
conditions = [];
|
|
// resource properties
|
|
resources_limits = new defaultDict(2);
|
|
resources_colors = new defaultDict(GOLD);
|
|
|
|
is_stochastic = false;
|
|
_lastsaved = null;
|
|
win = null;
|
|
effectList = []; // list of effects this happened this current time step
|
|
spriteDistribution = {};
|
|
movement_options = {};
|
|
all_objects = null;
|
|
|
|
lastcollisions = {};
|
|
steps = 0;
|
|
gameStates = [];
|
|
realGameStates = [];
|
|
keystate = {};
|
|
EOS = new EOS();
|
|
sprite_bonus_granted_on_timestep = -1; // to ensure you only grant bonus once per timestep (since you check _isDone() multiple times)
|
|
timeout_bonus_granted_on_timestep = -1; // to ensure you only grant bonus once per timestep (since you check _isDone() multiple times)
|
|
|
|
shieldedEffects = {};
|
|
paused = true;
|
|
|
|
FPS = false;
|
|
use_frame = false;
|
|
|
|
objectTypes = {};
|
|
|
|
no_players = 1;
|
|
|
|
ignoredattributes = [
|
|
"stypes",
|
|
"name",
|
|
"lastmove",
|
|
// 'color',
|
|
"lastrect",
|
|
"resources",
|
|
"physicstype",
|
|
"physics",
|
|
"rect",
|
|
"alternate_keys",
|
|
"res_type",
|
|
"stype",
|
|
"ammo",
|
|
"draw_arrow",
|
|
"shrink_factor",
|
|
"prob",
|
|
"is_stochastic",
|
|
"cooldown",
|
|
"total",
|
|
"is_static",
|
|
"noiseLevel",
|
|
"angle_diff",
|
|
"only_active",
|
|
"airsteering",
|
|
"strength",
|
|
];
|
|
|
|
constructor(args) {
|
|
this.args = args;
|
|
|
|
for (const argsKey in args) {
|
|
this[argsKey] = args[argsKey];
|
|
}
|
|
|
|
this.reset();
|
|
}
|
|
|
|
reset = () => {
|
|
this.score = 0;
|
|
this.bonus_score = 0;
|
|
this.real_start_time = 0;
|
|
this.real_time = 0;
|
|
this.time = 0;
|
|
this.ended = false;
|
|
this.num_sprites = 0;
|
|
this.kill_list = [];
|
|
this.all_killed = [];
|
|
this.paused = true;
|
|
this.shieldedEffects = {};
|
|
};
|
|
|
|
resetLevel = () => {
|
|
this.reset();
|
|
this.buildLevel(this.level);
|
|
};
|
|
|
|
buildLevel = (lstr) => {
|
|
let lines = lstr
|
|
.split("\n")
|
|
.map((l) => {
|
|
return l.trimEnd();
|
|
})
|
|
.filter((l) => {
|
|
return l.length > 0;
|
|
});
|
|
let lengths = lines.map((line) => line.length);
|
|
|
|
// console.assert(Math.min.apply(null, lengths) === Math.max.apply(null, lengths), "Inconsistent line lengths");
|
|
|
|
this.width = Math.max(...lengths);
|
|
this.height = lines.length;
|
|
|
|
// console.assert(this.width > 1 && this.height > 1, "Level too small");
|
|
|
|
//Set up resources
|
|
for (let res_type in this.sprite_constr) {
|
|
if (!this.sprite_constr.hasOwnProperty(res_type)) continue;
|
|
let [sclass, args, _] = this.sprite_constr[res_type];
|
|
if (new sclass(0, 0, args) instanceof Resource) {
|
|
if (args["res_type"]) {
|
|
res_type = args["res_type"];
|
|
}
|
|
if (args["color"]) {
|
|
this.resources_colors[res_type] = args["color"];
|
|
}
|
|
if (args["limit"]) {
|
|
this.resources_limits[res_type] = args["limit"];
|
|
}
|
|
} else {
|
|
this.sprite_groups[res_type] = [];
|
|
this.sprite_constr[res_type][1]["Z"] =
|
|
this.sprite_order.indexOf(res_type);
|
|
}
|
|
}
|
|
|
|
// create sprites
|
|
lines.forEach((line, row) => {
|
|
for (const col in line) {
|
|
let c = line[col];
|
|
if (c in this.char_mapping) {
|
|
const pos = [parseInt(col), row];
|
|
this._createSprite(this.char_mapping[c], pos);
|
|
} else if (c in this.default_mapping) {
|
|
const pos = [parseInt(col), row];
|
|
this._createSprite(this.default_mapping[c], pos);
|
|
}
|
|
}
|
|
});
|
|
|
|
this.kill_list = [];
|
|
|
|
this.collision_eff.forEach((item) => {
|
|
const effect = item[2];
|
|
if (stochastic_effects.indexOf(effect) !== -1) this.is_stochastic = true;
|
|
});
|
|
|
|
this.level = lstr;
|
|
|
|
// console.log(this.sprite_order)
|
|
};
|
|
|
|
getPath = (start, end) => {};
|
|
|
|
randomizeAvatar = () => {
|
|
if (this.getAvatars().length === 0) this._createSprite(["avatar"], [0, 0]);
|
|
};
|
|
|
|
current_avatar_idx = 0;
|
|
|
|
_createSprite = (keys, pos) => {
|
|
let res = [];
|
|
keys.forEach((key) => {
|
|
if (this.num_sprites > MAX_SPRITES) {
|
|
console.log("Sprite limit reached.");
|
|
return;
|
|
}
|
|
let [sclass, args, stypes] = this.sprite_constr[key];
|
|
let anyother = false;
|
|
|
|
stypes.reverse().forEach((pk) => {
|
|
if (this.singletons.contains(pk)) {
|
|
if (this.numSprites(pk) > 0) {
|
|
anyother = true;
|
|
}
|
|
}
|
|
});
|
|
if (anyother) return;
|
|
args.key = key;
|
|
let s = new sclass(pos, [1, 1], args);
|
|
s.stypes = stypes;
|
|
|
|
if(s instanceof MovingAvatar){
|
|
s.playerID = this.current_avatar_idx;
|
|
this.current_avatar_idx += 1;
|
|
}
|
|
|
|
if (this.sprite_groups[key]) this.sprite_groups[key].push(s);
|
|
else this.sprite_groups[key] = [s];
|
|
this.num_sprites += 1;
|
|
if (s.is_stochastic) this.is_stochastic = true;
|
|
res.push(s);
|
|
});
|
|
|
|
return res;
|
|
};
|
|
|
|
_createSprite_cheap = (key, pos) => {
|
|
const [sclass, args, stypes] = this.sprite_constr[key];
|
|
const s = new sclass(pos, [1, 1], args);
|
|
s.stypes = stypes;
|
|
this.sprite_groups[key].push(s);
|
|
this.num_sprites += 1;
|
|
return s;
|
|
};
|
|
|
|
_iterAll = (ignoreKilled = false, filter_noncollision = false) => {
|
|
if (this.sprite_order[this.sprite_order.length - 1] !== "avatar") {
|
|
this.sprite_order.remove("avatar");
|
|
this.sprite_order.push("avatar");
|
|
}
|
|
/**
|
|
* @type {VGDLSprite[]}
|
|
*/
|
|
const result = this.sprite_order.reduce((base, key) => {
|
|
if (this.sprite_groups[key] === undefined) return base;
|
|
if (ignoreKilled) {
|
|
return base.concat(
|
|
this.sprite_groups[key].filter((s) => !this.kill_list.includes(s)),
|
|
);
|
|
}
|
|
return base.concat(this.sprite_groups[key]);
|
|
}, []);
|
|
|
|
if (filter_noncollision) {
|
|
return result.filter((s) =>
|
|
s.stypes.filter(x => this.collision_types.includes(x)).length > 0
|
|
)
|
|
}
|
|
return result;
|
|
};
|
|
|
|
_iterAllExcept = (keys) => {
|
|
if (this.sprite_order[this.sprite_order.length - 1] !== "avatar") {
|
|
this.sprite_order.remove("avatar");
|
|
this.sprite_order.push("avatar");
|
|
}
|
|
|
|
return this.sprite_order.reduce((base, key) => {
|
|
if (key in keys) return base;
|
|
if (this.sprite_groups[key] === undefined) return base;
|
|
return base.concat(this.sprite_groups[key]);
|
|
}, []);
|
|
};
|
|
|
|
numSprites = (key) => {
|
|
const deleted = this.kill_list.filter((s) => {
|
|
return s.stypes[key];
|
|
}).length;
|
|
// if (key in this.sprite_groups) {
|
|
// //避免找父类情况
|
|
// if(this.sprite_groups[key].length !== 0)
|
|
// return this.sprite_groups[key].length-deleted;
|
|
// }
|
|
|
|
return (
|
|
this._iterAll().filter((s) => {
|
|
return s.stypes.contains(key);
|
|
}).length - deleted
|
|
); // Should be __iter__ - deleted
|
|
};
|
|
|
|
/**
|
|
*
|
|
* @param {string} key sprite type
|
|
* @returns {list[Object]}
|
|
*/
|
|
getSprites = (key) => {
|
|
if (this.sprite_groups[key] instanceof Array)
|
|
return this.sprite_groups[key].filter((s) => {
|
|
return this.kill_list.indexOf(s) === -1;
|
|
});
|
|
else
|
|
return this._iterAll().filter((s) => {
|
|
return s.stypes.contains(key) && this.kill_list.indexOf(s) === -1;
|
|
});
|
|
};
|
|
|
|
getAvatars = (key) => {
|
|
const res = [];
|
|
if (this.sprite_groups.hasOwnProperty(key)) {
|
|
const ss = this.sprite_groups[key];
|
|
if (ss && ss[0] instanceof MovingAvatar)
|
|
res.concat(ss.filter((s) => this.kill_list.indexOf(s) === -1));
|
|
}
|
|
return res;
|
|
};
|
|
|
|
getSubTypes = (key) => {
|
|
const getSub = (dict, key) => {
|
|
if (dict.hasOwnProperty(key)) {
|
|
return dict[key];
|
|
} else if (Object.keys(dict).length === 0) {
|
|
return null;
|
|
} else {
|
|
let result = null;
|
|
for (const child in dict) {
|
|
const a = getSub(dict[child], key);
|
|
if (a !== null) result = a;
|
|
}
|
|
return result;
|
|
}
|
|
};
|
|
return getSub(this.objectTypes, key);
|
|
};
|
|
|
|
getObjects = () => {
|
|
const obj_list = {};
|
|
const fs = this.getFullState();
|
|
const obs = Object.copy(fs["objects_cur"]);
|
|
|
|
for (let obj_type in obs) {
|
|
this.getSprites(obj_type).forEach((obj) => {
|
|
const features = {
|
|
color: colorDict[obj.color.toString()],
|
|
row: [obj.location.y],
|
|
};
|
|
const type_vector = {
|
|
color: colorDict[obj.color.toString()],
|
|
row: [obj.location.y],
|
|
};
|
|
obj_list[obj.ID] = {
|
|
sprite: obj,
|
|
position: [obj.location.x, obj.location.y],
|
|
features: features,
|
|
type: type_vector,
|
|
};
|
|
});
|
|
}
|
|
|
|
return obj_list;
|
|
};
|
|
|
|
getFullState = () => {
|
|
const ias = this.ignoredattributes;
|
|
const obs = {};
|
|
const killed = {};
|
|
/**
|
|
* @type {VGDLSprite[]}
|
|
*/
|
|
const objects = [];
|
|
const actions = Object.keys(this.keystate).filter((key) => {
|
|
return this.keystate[key];
|
|
});
|
|
this.steps += actions.length;
|
|
|
|
for (const key in this.sprite_groups) {
|
|
if (!this.sprite_groups.hasOwnProperty(key)) continue;
|
|
const ss = {};
|
|
const order = this.sprite_order.indexOf(key);
|
|
this.getSprites(key).forEach((s) => {
|
|
const attrs = {};
|
|
Object.keys(s).forEach((a) => {
|
|
let val = s[a];
|
|
if (ias.indexOf(a) === -1) {
|
|
attrs[a] = val;
|
|
}
|
|
attrs["Z"] = order;
|
|
});
|
|
if (s.resources) {
|
|
attrs["resources"] = s.resources; // Should be object
|
|
}
|
|
|
|
ss[s.ID] = Object.copy(attrs);
|
|
objects.push(s);
|
|
});
|
|
obs[key] = Object.copy(ss);
|
|
}
|
|
|
|
const object_cur = Object.copy(obs);
|
|
|
|
return {
|
|
frame: this.time,
|
|
score: this.bonus_score,
|
|
ended: this.ended,
|
|
win: this.win,
|
|
objects: objects,
|
|
objects_cur: object_cur,
|
|
killed: killed,
|
|
actions: actions,
|
|
events: this.effectList,
|
|
real_time: this.real_time,
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Updates all sprites in the game.
|
|
* @param {number} delta - The time delta since the last update.
|
|
* This method iterates through all sprites and calls their update method, passing the game instance.
|
|
* If a sprite is not crashed, it will be updated; otherwise, it is skipped.
|
|
*/
|
|
_updateAll = (delta) => {
|
|
this._iterAll().forEach((sprite) => {
|
|
// try {
|
|
if (!sprite.crashed) sprite.update(this, delta);
|
|
// } catch (err) {
|
|
// if ((!sprite.crashed)) {
|
|
// console.error('could not update', sprite.name)
|
|
// throw err
|
|
// sprite.crashed = true;
|
|
// }
|
|
|
|
// }
|
|
});
|
|
};
|
|
|
|
_subUpdate = (sub_idx, sum_idx) => {
|
|
this._iterAll().forEach((sprite) => {
|
|
if (!sprite.crashed) sprite.subUpdate(this, sub_idx, sum_idx);
|
|
});
|
|
}
|
|
|
|
_clearAll = (onscreen = true) => {
|
|
this.kill_list.forEach((s) => {
|
|
this.all_killed.push(s);
|
|
this.sprite_groups[s.name].remove(s);
|
|
});
|
|
|
|
this.kill_list = [];
|
|
this.shieldedEffects = {};
|
|
};
|
|
|
|
_updateCollisionDict = (changedsprite) => {
|
|
for (let key in changedsprite.stypes) {
|
|
if (key in this.lastcollisions) delete this.lastcollisions[key];
|
|
}
|
|
};
|
|
|
|
_terminationHandling = () => {
|
|
let break_loop = false;
|
|
this.terminations.forEach((t) => {
|
|
//if (break_loop) return;
|
|
if (break_loop) return;
|
|
const [ended, win] = t.isDone(this);
|
|
|
|
this.ended = ended;
|
|
if (this.ended) {
|
|
if (win) {
|
|
//if (this.score <= 0)
|
|
//this.score += 1;
|
|
this.win = true;
|
|
} else {
|
|
//this.score -= 1;
|
|
this.win = false;
|
|
}
|
|
break_loop = true;
|
|
}
|
|
});
|
|
};
|
|
|
|
_getAllSpriteGroups = () => {
|
|
const lastcollisions = {};
|
|
|
|
this.collision_eff.forEach((eff) => {
|
|
const [class1, class2, effect, kwargs] = eff;
|
|
|
|
[class1, class2].forEach((sprite_class) => {
|
|
if (!(sprite_class in lastcollisions)) {
|
|
let sprite_array = [];
|
|
if (sprite_class in this.sprite_groups) {
|
|
sprite_array = this.sprite_groups[sprite_class].slice();
|
|
} else {
|
|
const sprites_array = [];
|
|
Object.keys(this.sprite_groups).forEach((key) => {
|
|
const sprites = this.sprite_groups[key].slice();
|
|
if (sprites.length && sprites[0].stypes.contains(sprite_class)) {
|
|
sprite_array = sprite_array.concat(sprites);
|
|
}
|
|
});
|
|
}
|
|
lastcollisions[sprite_class] = sprite_array;
|
|
}
|
|
});
|
|
});
|
|
return Object.assign(lastcollisions, this.sprite_groups);
|
|
};
|
|
|
|
_multi_effect = () => {
|
|
function r(sprite, partner, game, kwargs) {
|
|
let value;
|
|
for (let i = 0; i < arguments.length; i++) {
|
|
value = arguments[i](sprite, partner, game, kwargs);
|
|
}
|
|
return value;
|
|
}
|
|
return r;
|
|
};
|
|
|
|
get_effect = (stypes1, stypes2) => {
|
|
const res = [];
|
|
this.collision_eff.forEach((eff) => {
|
|
const class1 = eff[0];
|
|
const class2 = eff[1];
|
|
const eclass = eff[2];
|
|
|
|
if (
|
|
this.shieldedEffects[class1] && includesArray(this.shieldedEffects[class1], [class2, eclass])
|
|
) {
|
|
// console.log(
|
|
// `[GAMES] Check shield ${class1}, ${class2}, return`,
|
|
// );
|
|
return;
|
|
}
|
|
|
|
if (stypes1.includes(class1) && stypes2.includes(class2))
|
|
res.push({ reverse: false, effect: eff[2], kwargs: eff[3] });
|
|
else if (stypes1.includes(class2) && stypes2.includes(class1))
|
|
res.push({ reverse: true, effect: eff[2], kwargs: eff[3] });
|
|
});
|
|
return res;
|
|
};
|
|
|
|
_effectHandling = () => {
|
|
//最多处理碰撞7次
|
|
for (let iter_time = 0; iter_time < 7; iter_time++) {
|
|
// console.log("EffectHandling", iter_time)
|
|
this.updateCollision();
|
|
|
|
if (this.collision_set.length === 0) return;
|
|
|
|
// 确保collision按照顺序执行
|
|
for(let eff of this.collision_eff){
|
|
const class1 = eff[0]
|
|
const class2 = eff[1]
|
|
const eclass = eff[2]
|
|
const used_collision = [];
|
|
for (const collision of this.collision_set) {
|
|
const stypes1 = collision[0].stypes;
|
|
const stypes2 = collision[1].stypes;
|
|
|
|
let effects = [];
|
|
|
|
if (collision[1] === "EOS" || collision[1] === "eos") {
|
|
if (
|
|
stypes1.includes(class1) &&
|
|
(class2 === "EOS" || class2 === "eos")
|
|
)
|
|
effects = [{ reverse: false, effect: eff[2], kwargs: eff[3] }];
|
|
}
|
|
else {
|
|
if (
|
|
this.shieldedEffects[class1] && includesArray(this.shieldedEffects[class1], [class2, eclass])
|
|
) {
|
|
return;
|
|
}
|
|
|
|
if (stypes1.includes(class1) && stypes2.includes(class2))
|
|
effects.push({ reverse: false, effect: eff[2], kwargs: eff[3] });
|
|
else if (stypes1.includes(class2) && stypes2.includes(class1))
|
|
effects.push({ reverse: true, effect: eff[2], kwargs: eff[3] });
|
|
};
|
|
|
|
if (effects.length === 0) continue;
|
|
used_collision.push(collision);
|
|
for (const effect_set of effects) {
|
|
let [sprite, partner] = [collision[0], collision[1]];
|
|
if (effect_set.reverse) {
|
|
[sprite, partner] = [collision[1], collision[0]];
|
|
}
|
|
|
|
effect_set.effect(sprite, partner, this, effect_set.kwargs);
|
|
}
|
|
}
|
|
// this.collision_set = this.collision_set.filter((c) => !used_collision.includes(c));
|
|
}
|
|
this.collision_set = [];
|
|
}
|
|
|
|
};
|
|
|
|
run = (on_game_end) => {
|
|
this.on_game_end = on_game_end;
|
|
return this.startGame;
|
|
};
|
|
|
|
presskey = (keyCode, playerID = 0) => {
|
|
// console.log(`Press Button: ${keyCode}`)
|
|
if (this.key_handler === "Pulse") return;
|
|
const key = `${keyCode}${playerID}`;
|
|
// console.log(`Presskey`, key)
|
|
this.keystate[key] = true;
|
|
// this.key_to_clean?.push(key);
|
|
// if (this.key_handler === "Pulse") {
|
|
// this.update(0, true);
|
|
// }
|
|
};
|
|
|
|
presskeyUp = (keyCode, playerID = 0) => {
|
|
const key = `${keyCode}${playerID}`;
|
|
if (this.key_handler === "Pulse") {
|
|
this.keystate[key] = true;
|
|
this.key_to_clean?.push(key);
|
|
return;
|
|
}
|
|
this.keystate[key] = false;
|
|
this.key_to_clean?.push(key);
|
|
// this.update(0, true)
|
|
};
|
|
|
|
// updateTime = 1000 / 10;
|
|
updateTime = 1/15;
|
|
currentTime = 0;
|
|
|
|
collision_set = [];
|
|
|
|
addCollisions = (a, b) => {
|
|
return;
|
|
};
|
|
|
|
addShield = (a, stype, ftype) => {
|
|
|
|
for(const a_type of a){
|
|
if(!this.shieldedEffects[a_type])
|
|
this.shieldedEffects[a_type] = []
|
|
if(includesArray(this.shieldedEffects[a_type], [stype, ftype])) continue;
|
|
this.shieldedEffects[a_type].push([stype, ftype]);
|
|
}
|
|
// console.log("add shield", a, stype, ftype, this.shieldedEffects)
|
|
};
|
|
|
|
updateCollision = () => {
|
|
//TODO: 使用最简单的方法实现,到非格子的方法可能会有问题
|
|
// console.log("iter all")
|
|
const allSprites = this._iterAll(true, true);
|
|
|
|
// console.log("updateCollision", allSprites.length)
|
|
|
|
|
|
for (let i = 0; i < allSprites.length; i++) {
|
|
const sprite1 = allSprites[i];
|
|
|
|
if (
|
|
sprite1.location.x < 0 ||
|
|
sprite1.location.x > this.width ||
|
|
sprite1.location.y < 0 ||
|
|
sprite1.location.y > this.height
|
|
) {
|
|
this.collision_set.push([sprite1, "EOS"]);
|
|
}
|
|
|
|
for (let j = i + 1; j < allSprites.length; j++) {
|
|
const sprite2 = allSprites[j];
|
|
const dist = quickDistance(sprite1, sprite2);
|
|
|
|
if (dist <= 0.99) {
|
|
this.collision_set.push([sprite1, sprite2]);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
*
|
|
* @param {number} delta, time in seconds
|
|
* @param {boolean} now, if true, update will be called immediately
|
|
* @returns {null | string | undefined} return null if game is paused, return string if game is ended, return undefined if game is running
|
|
*/
|
|
update = (delta, now = false) => {
|
|
// if (this.key_handler === "Pulse") {
|
|
// if (!now) return;
|
|
// }
|
|
|
|
if (this.use_frame === false){
|
|
if (!now) {
|
|
this.currentTime += delta;
|
|
// console.log("BasicGame update", now, this.use_frame, delta, this.use_frame, this.currentTime, this.updateTime, this.currentTime < this.updateTime)
|
|
|
|
if (this.currentTime < this.updateTime) {
|
|
// console.log("return")
|
|
return;
|
|
}
|
|
this.currentTime %= this.updateTime;
|
|
}
|
|
}
|
|
|
|
if (this.paused) return "paused";
|
|
if (this.ended) {
|
|
this.paused = true;
|
|
this.on_game_end(this.getFullState());
|
|
return this.win;
|
|
}
|
|
|
|
|
|
if(this.use_frame === true)
|
|
this.time += delta * 15;
|
|
else this.time ++;
|
|
|
|
this._terminationHandling();
|
|
|
|
this._clearAll();
|
|
this._updateAll();
|
|
|
|
this._effectHandling();
|
|
|
|
for (const keycode of this.key_to_clean) {
|
|
this.keystate[keycode] = false;
|
|
}
|
|
|
|
this.key_to_clean = [];
|
|
this.collision_set = [];
|
|
};
|
|
|
|
/**
|
|
* Start the game.
|
|
*/
|
|
startGame = () => {
|
|
this.reset();
|
|
this.paused = false;
|
|
|
|
this.key_to_clean = [];
|
|
|
|
let sprite_types = [Immovable];
|
|
this.all_objects = this.getObjects(); // Save all objects, some which may be killed in game
|
|
|
|
const objects = this.getObjects();
|
|
this.spriteDistribution = {};
|
|
this.movement_options = {};
|
|
Object.keys(objects).forEach((sprite) => {
|
|
this.spriteDistribution[sprite] = initializeDistribution(sprite_types);
|
|
this.movement_options[sprite] = { OTHER: {} };
|
|
sprite_types.forEach((sprite_type) => {
|
|
this.movement_options[sprite][sprite_type] = {};
|
|
});
|
|
});
|
|
|
|
// This should actually be in a game loop function, or something.
|
|
|
|
// this._clearAll();
|
|
|
|
Object.keys(objects).forEach((sprite_number) => {
|
|
let sprite = objects[sprite_number];
|
|
if (!(this.spriteDistribution in sprite)) {
|
|
this.all_objects[sprite] = objects[sprite];
|
|
this.spriteDistribution[sprite] = initializeDistribution(sprite_types);
|
|
this.movement_options[sprite] = { OTHER: {} };
|
|
sprite_types.forEach((sprite_type) => {
|
|
this.movement_options[sprite][sprite_type] = {};
|
|
});
|
|
}
|
|
});
|
|
|
|
this.keystate = {};
|
|
this.keywait = {};
|
|
|
|
// cache collision types
|
|
this.collision_types = [];
|
|
this.collision_eff.forEach((eff) => {
|
|
const object = eff[0];
|
|
const subject = eff[1];
|
|
if(!this.collision_types.includes(object))
|
|
this.collision_types.push(object)
|
|
if(!this.collision_types.includes(subject))
|
|
this.collision_types.push(subject)
|
|
});
|
|
// console.log(this.collision_types)
|
|
};
|
|
|
|
getPossibleActions = () => {
|
|
return this.getAvatars()[0].declare_possible_actions();
|
|
};
|
|
}
|