DPTW/Public/ecma/DPTW.ecma.js

838 lines
31 KiB
JavaScript
Executable File
Raw Permalink 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.

DPTW = function(input){
const self = this,
default_settings = {
object_name : "dptw",
dptw_autostart : true,
settings_files : [
"/json/DPTW.settings.json",
"/json/DPTW.settings.secrets.json"
],
position : "body",
ajax_timeout : 2000,
dictionary_file : "/json/DPTW.dictionary.json",
class : "dptw"
},
sprites = {},
sounds = {},
images = {};
let started = false,
game_working = false,
game_thread = null,
difficulty, character_size, last_word_y, velocity, velocity_increment, current_velocity,
text_box, text_cache = "",
destruction_animations, destruction_size, destruction_sounds,
overwrite_sprites, overwrite_sounds,
scream_sounds, ok_sounds,
characters_sizer, reduce_life_value, increase_life_value, allow_alter_style, characters_color,
allow_reduce_score, character_ok_points, character_wrong_points, word_ok_points, word_wrong_points,
multiplier, allow_points_multiplier,
zeros_fill_score, allow_pause,
game_status = "loading",
font_family, main_background, secondary_background,
session_time_update, session_last_update = 0,
sessions_show_update_ok_message,
sessions_show_update_error_message,
word_number, word_proportional_timeout, last_word_time, word_enabled_random_timer,
allow_ignore_case,
multiplier_value;
this.sql_session_errors = [
"sql_exception",
"session_string_null",
"session_empty",
"session_bad_format",
"session_id_string_empty",
"session_id_string_bad",
"session_string_incompleted",
"session_null",
"session_not_id",
"session_not_exists",
"session_date_out",
"session_timeout",
null
];
let main_menu = self.main_menu;
let kanvas = self.kanvas;
let i18n = this.i18n;
let errors = this.errors;
let dictionary = this.dictionary;
let scores = this.scores;
const GAME = 2;
const MAIN_MENU = 5;
const FPS = 4;
const WORDS = 0;
const SHOTS = 1;
const DESTRUCTIONS = 2;
const LIFE = 6;
const PAUSE = 7;
const SCORE = this.SCORE = 8;
const construct = () => {
self.kanvas = kanvas = new Kanvas({
...default_settings,
...(typeof input != "object" ? {} : input),
autostart : false
});
kanvas.extends(self);
DPTW.I18N && (i18n = self.i18n = new DPTW.I18N(self, input));
DPTW.Errors && (errors = self.errors = new DPTW.Errors(self, input));
DPTW.Dictionary && (dictionary = self.dictionary = new DPTW.Dictionary(self, input));
DPTW.Scores && (scores = self.scores = new DPTW.Scores(self, input));
self.settings("dptw_autostart") && self.start();
};
this.start = callback => {
const end = status => typeof callback == "function" && callback(status);
if(started){
end(false);
return false;
};
started = true;
self.load_blocks(
self.set_array(self.settings("settings_files")),
json => self.settings_add(json, true),
() => {
difficulty = self.settings("difficulty");
velocity = self.settings("velocity");
velocity_increment = self.settings("velocity_increment");
destruction_size = self.settings("destruction_size");
overwrite_sprites = self.settings(["overwrite_sprites", "overwrite"]);
overwrite_sounds = self.settings(["overwrite_sounds", "overwrite"]);
characters_sizer = self.settings("characters_sizer");
reduce_life_value = self.settings("reduce_life_value");
increase_life_value = self.settings("increase_life_value");
allow_alter_style = self.settings("allow_alter_style");
characters_color = self.settings("characters_color");
word_proportional_timeout = self.settings("word_proportional_timeout");
word_enabled_random_timer = self.settings("word_enabled_random_timer");
allow_ignore_case = self.settings("allow_ignore_case");
allow_reduce_score = self.settings("allow_reduce_score");
character_ok_points = self.settings("character_ok_points");
word_ok_points = self.settings("word_wrong_points");
character_wrong_points = self.settings("character_wrong_points");
word_wrong_points = self.settings("word_wrong_points");
allow_points_multiplier = self.settings("allow_points_multiplier");
zeros_fill_score = self.settings("zeros_fill_score");
allow_pause = self.settings("allow_pause");
multiplier_value = self.settings("multiplier_value");
font_family = self.settings("font_family");
main_background = self.settings("main_background");
secondary_background = self.settings("secondary_background");
session_time_update = self.settings("session_time_update");
sessions_show_update_ok_message = dptw.settings("sessions_show_update_ok_message");
sessions_show_update_error_message = dptw.settings("sessions_show_update_error_message");
i18n.start(status => {
self.load_sprites(self.settings("sprites_files"), true, () => {
const destruction_animations_keys = self.settings("destruction_animations");
destruction_animations = [];
(
destruction_animations_keys instanceof Array ? destruction_animations_keys : typeof destruction_animations_keys == "string" ? [destruction_animations_keys] : []
).forEach(key => sprites[key] && destruction_animations.push({
sprite : sprites[key].path,
animation : sprites[key].animation,
frames_per_second : sprites[key].frames_per_second
}));
self.load_sounds(self.settings("sounds_files"), true, () => {
const sets = {
destruction_sounds : destruction_sounds = [],
scream_sounds : scream_sounds = [],
ok_sounds : ok_sounds = []
};
for(const key in sets){
const set = self.settings(key);
(set instanceof Array ? set : typeof set == "string" ? [set] : []).forEach(subkey => sounds[subkey] && sets[key].push({
sound : sounds[subkey].path,
fragments : sounds[subkey].fragments,
volume : sounds[subkey].volume || 1
}));
};
kanvas.start(status => {
dictionary.start(status => {
scores.start(status => {
builder();
game_thread = dptw.threads_add(game_thread_method);
end(status);
});
});
});
});
});
});
},
0
);
return true;
};
this.load_file = (url, callback) => {
let ended = false;
const ajax = new XMLHttpRequest(),
end = error_message => {
if(ended)
return;
ended = true;
callback(ajax.responseText, ajax.status, ajax.readyState, error_message == "OK", error_message);
},
date = Date.now(),
timeout = self.settings(["ajax_timeout", "timeout"]);
ajax.open("get", url, true);
ajax.timeout = timeout;
ajax.onreadystatechange = () => {
if(ended)
return;
if(ajax.readyState == 4)
end((ajax.status >= 200 && ajax.status < 300) || [301, 302, 304].includes(ajax.status) ? "OK" : "HTTP_ERROR");
else if(Date.now() - date > timeout)
end("FORCED_TIMEOUT");
};
ajax.send(null);
ajax.onerror = () => end("ERROR");
ajax.onabort = () => end("ABORTED");
ajax.ontimeout = () => end("TIMEOUT");
return ajax;
};
this.set_array = variable => (
variable instanceof Array ? variable :
["string", "object"].includes(typeof variable) ? [variable] :
[])
this.load_blocks = (inputs, fragment_callback, callback, i) => {
if(i == inputs.length){
typeof callback == "function" && callback();
return;
};
const end = () => self.load_blocks(inputs, fragment_callback, callback, i + 1);
if(inputs[i]){
if(inputs[i] instanceof Array)
self.load_blocks(inputs[i], fragment_callback, end, 0);
else if(typeof inputs[i] == "object"){
fragment_callback(inputs[i]);
end();
}else if(typeof inputs[i] == "string"){
let json;
try{
json = JSON.parse(inputs[i]);
}catch(exception){};
if(json)
self.load_blocks(self.set_array(json), fragment_callback, end, 0);
else
self.load_file(inputs[i], response => {
try{
json = JSON.parse(response);
}catch(exception){};
self.load_blocks(self.set_array(json), fragment_callback, end, 0);
});
}else
end();
}else
end();
};
this.load_sprites = (inputs, overwrite, callback) => this.load_blocks(inputs instanceof Array ? inputs : inputs ? [inputs] : [], json => {
typeof overwrite != "boolean" && (overflow = overwrite_sprites);
(json.sprites || []).forEach(sprite => {
if(
sprite &&
sprite.enabled &&
sprite.key
){
if(sprite.animation)
(overwrite || !sprites[sprite.key]) && (sprites[sprite.key] = sprite);
else
(overwrite || !images[sprite.key]) && (images[sprite.key] = sprite);
};
});
}, callback, 0);
this.load_sounds = (inputs, overwrite, callback) => this.load_blocks(inputs instanceof Array ? inputs : inputs ? [inputs] : [], json => {
typeof overwrite != "boolean" && (overflow = overwrite_sounds);
(json.sounds || []).forEach(sound => (
sound &&
sound.enabled &&
sound.key &&
(overwrite || !sounds[sound.key]) &&
(sounds[sound.key] = sound)
));
}, callback, 0);
const create_button = (key, x, y, width, height) => ({
type : "rectangle",
x : x,
y : y,
width : width || 16,
height : height || 3,
background : "rgba(255, 255, 255, .3)",
on_click : "{object_name}.go_to_" + key,
childs : [{
type : "text",
x : 8,
y : .5,
align : "center",
baseline : "top",
text : i18n.get(key),
background : "#FFF",
size : 2,
border_width : .05,
border_color : "#000",
style : "bold",
family : font_family,
shadows : [
[0, 0, .125, "#000"],
[0, 0, .25, "#000"],
[0, 0, .5, "#000"],
[0, 0, 1, "#000"]
]
}]
});
const show_menu = this.show_menu = () => {
const half = kanvas.cells / 2,
menu = {type : "block", x : -half, y : -half, childs : [
{type : "rectangle", x : 0, y : 0, width : kanvas.cells, height : kanvas.cells, background : "#000", alpha : .7},
{type : "block", x : half - 8, y : half - 9.5, childs : [{
type : "text",
x : 8,
y : -5,
align : "center",
baseline : "top",
text : i18n.get("test_mode"),
background : "#FFF",
size : 4,
border_width : .05,
border_color : "#000",
style : "bold",
family : font_family,
shadows : [
[0, 0, .25, "#000"]
]
}]}
]};
game_status = "main_menu";
["start", "settings", "scores", "manual", "credits"].forEach((key, i) => menu.childs[1].childs.push(create_button(key, 0, (i * 4) + .5)));
kanvas.map[MAIN_MENU] = menu;
};
const hide_menu = () => kanvas.map[MAIN_MENU] = null;
const builder = () => {
const half = kanvas.cells / 2,
margin = kanvas.cells / 8,
padding = half + margin,
full_padding = padding * 2,
margined = half - margin;
game_status = "building";
errors.build_toast();
kanvas.map.push(
{type : "rectangle", x : -kanvas.cells_x, y : -kanvas.cells_y, width : kanvas.cells_x * 2, height : kanvas.cells_y * 2, background : "#000", childs : images[secondary_background] ? [
{type : "image", x : 0, y : 0, width : kanvas.cells_x * 2, height : kanvas.cells_y * 2, url : images[secondary_background].path},
{type : "rectangle", x : 0, y : 0, width : kanvas.cells_x, height : kanvas.cells_y, background : "#000", alpha : .85}
] : []},
{type : "cache", name : "main_background_back", x : -padding, y : -padding, width : full_padding, height : full_padding, childs : [
images[main_background] ? {type : "image", x : -half, y : -half, width : kanvas.cells, height : kanvas.cells, url : images[main_background].path} : null,
{type : "rectangle", x : -half, y : -half, width : kanvas.cells, height : kanvas.cells, background : "#246", alpha : .5}
]},
null,
{type : "cache", name : "main_background_fore", x : -half, y : -half, width : kanvas.cells, height : kanvas.cells, childs : [
{type : "rectangle", x : -half, y : -half, width : kanvas.cells, height : margin, background : [0, -half, 0, -margined, [
[0, "#000"],
[1, "rgba(0, 0, 0, 0)"]
]]},
{type : "rectangle", x : -half, y : -half, width : margin, height : kanvas.cells, background : [-half, 0, -margined, 0, [
[0, "#000"],
[1, "rgba(0, 0, 0, 0)"]
]]},
{type : "rectangle", x : -half, y : margined, width : kanvas.cells, height : margin, background : [0, half, 0, margined, [
[0, "#000"],
[1, "rgba(0, 0, 0, 0)"]
]]},
{type : "rectangle", x : margined, y : -half, width : margin, height : kanvas.cells, background : [half, 0, margined, 0, [
[0, "#000"],
[1, "rgba(0, 0, 0, 0)"]
]]},
{type : "rectangle", x : -half, y : -half, width : kanvas.cells, height : kanvas.cells, border_color : "#147", border_width : .4, shadow : [
[0, 0, .5, "#28F"]
]}
]},
{type : "text", x : kanvas.cells_x, y : -kanvas.cells_y, align : "right", baseline : "top", text : "0.00", background : "#FFF"},
null,
{type : "rectangle", x : -half + 1, y : -half + 1, width : kanvas.cells - 2, height : 1, alpha : .3, border_color : "#000", border_width : .1, background : "#F00", shadow : [
[0, 0, .1, "#FFF"]
], childs : [
{type : "rectangle", x : .1, y : .1, width : kanvas.cells - 2.2, full : kanvas.cells - 2.2, height : .8, background : "#0F0"}
]},
null,
{type : "text", x : kanvas.cells_x, y : kanvas.cells_y, baseline : "bottom", align : "right", text : zeros_fill_score, style : "bold", background : "#FFF", size : 2},
null
);
show_menu();
kanvas.on_screen_change.add(() => {
with(kanvas.map[0]){
x = -kanvas.cells_x;
y = -kanvas.cells_y;
};
[kanvas.map[0], kanvas.map[0].childs[0], kanvas.map[0].childs[1]].forEach(level => {
if(level){
level.width = kanvas.cells_x * 2;
level.height = kanvas.cells_y * 2;
};
});
with(kanvas.map[FPS]){
x = kanvas.cells_x - 1;
y = -kanvas.cells_y + .3;
};
with(kanvas.map[SCORE]){
x = kanvas.cells_x;
y = kanvas.cells_y;
};
});
kanvas.threads_add(() => {
kanvas.map[FPS].text = kanvas.get_real_fps().toFixed(2);
});
text_box = kanvas.item_self.appendChild(document.createElement("textarea"));
["up", "down"].forEach(action => text_box.addEventListener("key" + action, check_characters));
kanvas.item_self.querySelector(".kanvas-ui").addEventListener("click", set_focus);
};
this.go_to_start = (item, event) => {
hide_menu();
start_game();
};
this.go_to_settings = (item, event) => console.log(["settings", item, event]);
this.go_to_scores = (item, event) => {
kanvas.map[MAIN_MENU] = null;
scores.build();
};
this.go_to_manual = (item, event) => window.open("/doc", "_blank");
this.go_to_credits = (item, event) => console.log(["credits", item, event]);
const start_game = () => {
self.map[GAME] = {type : "block", x : -kanvas.cells / 2, y : -kanvas.cells / 2, childs : [
{type : "block", x : 0, y : 0, childs : []},
{type : "block", x : 0, y : 0, childs : []},
{type : "block", x : 0, y : 0, childs : []}
]};
character_size = kanvas.cells / dictionary.maximum_characters,
last_word_y = kanvas.cells;
current_velocity = 0 + velocity;
text_cache = "";
text_box.value = "";
self.map[LIFE].childs[0].width = self.map[LIFE].childs[0].full;
self.map[GAME].childs[WORDS].childs = [];
kanvas.map[SCORE].text = zeros_fill_score;
multiplier = 1;
word_number = 0;
last_word_time = 0;
game_status = "running";
game_working = true;
};
const reduce_life = () => {
if((self.map[LIFE].childs[0].width -= reduce_life_value * difficulty) < 0){
self.map[LIFE].childs[0].width = 0;
end_game();
};
};
const increase_life = () => (
(self.map[LIFE].childs[0].width += increase_life_value * (1 - difficulty)) > self.map[LIFE].childs[0].full &&
(self.map[LIFE].childs[0].width = self.map[LIFE].childs[0].full
));
const set_score = points => {
if(allow_reduce_score || points > 0){
const new_points = Number(kanvas.map[SCORE].text) + points;
kanvas.map[SCORE].text = (zeros_fill_score + (new_points < 0 ? 0 : new_points)).slice(-zeros_fill_score.length);
};
};
const game_thread_method = () => {
const date = Date.now();
if(date - session_last_update > session_time_update){
session_last_update = date;
self.load_file("/api/sessions/update", response => {
console.log(response);
response = JSON.parse(response);
session_last_update = Date.now();
errors.validate(
response.content.error,
[].concat(self.sql_session_errors),
{},
sessions_show_update_error_message && "session_update_error",
sessions_show_update_ok_message && "session_update_ok"
);
});
};
if(!game_working)
return;
const real_velocity = (current_velocity += (kanvas.delta_time * velocity_increment * difficulty));
if(
!word_number ||
(
word_enabled_random_timer &&
last_word_y >= character_size && Math.random() * (1 / kanvas.get_real_fps()) < difficulty / 10000
) ||
(
word_proportional_timeout &&
date - last_word_time > word_proportional_timeout * (1 - (real_velocity / kanvas.cells))
)
){
let i = 0;
const l = self.map[GAME].childs[WORDS].childs.length,
word = dictionary.get_random_word();
last_word_y = 0;
word_number ++;
last_word_time = date;
for(; i < l; i ++)
if(!self.map[GAME].childs[WORDS].childs[i])
break;
const x = Math.random() * (dictionary.maximum_characters - word.length + 1) >> 0,
characters = [],
half = character_size / 2;
word.split("").forEach((character, j) => characters.push({
type : "text",
x : (character_size * j) + half,
y : half,
align : "center",
baseline : "middle",
text : character,
size : character_size * (allow_alter_style ? (1 - characters_sizer) + (Math.random() * characters_sizer) : 1),
style : allow_alter_style ? Math.random() > .5 ? "bold" : null : "bold",
background : characters_color || "rgb(" + [0, 0, 0].map(_ => Math.random() * 256 >> 0).join(",") + ")",
shadow : [
[0, 0, .5, "#FFF"],
[0, 0, .35, "#FFF"],
[0, 0, .2, "#000"]
]
}));
self.map[GAME].childs[WORDS].childs[i] = {type : "block", x : character_size * x, y : 0, alpha : 0, childs : characters};
};
last_word_y += real_velocity;
self.map[GAME].childs[WORDS].childs.forEach((word, j) => {
if(word){
if(word.y > kanvas.cells - character_size){
word.childs.forEach(character => {
if(!character)
return;
set_destruction(word.x + character.x, word.y);
play_audios_from(scream_sounds);characters_color
reduce_life();
set_score(character_wrong_points);
});
self.map[GAME].childs[WORDS].childs[j] = null;
set_score(word_wrong_points);
multiplier = 1;
}else{
word.alpha != 1 && (word.alpha += real_velocity / 5) > 1 && (word.alpha = 1);
word.y += real_velocity;
};
};
});
};
const set_focus = event => text_box.focus();
this.go_to_resume = () => self.end_pause();
this.go_to_left_match = () => {
self.end_pause();
end_game();
};
this.start_pause = () => {
if(allow_pause && game_status == "running"){
const half = kanvas.cells / 2;
game_status = "stopped";
game_working = false;
kanvas.map[PAUSE] = {type : "block", x : -half, y : -half, childs : [
{type : "rectangle", x : 0, y : 0, width : kanvas.cells, height : kanvas.cells, background : "#000", alpha : .7},
{type : "text", x : half, y : half - 10, baseline : "middle", align : "center", text : i18n.get("paused"), size : 4, style : "bold", shadow : [
[0, 0, .35, "#BBB"]
]},
create_button("resume", half - 8, half - 3),
create_button("left_match", half - 8, half + 1)
]}
return true;
};
return false;
};
this.end_pause = () => {
if(game_status == "stopped"){
game_status = "running";
game_working = true;
kanvas.map[PAUSE] = null;
return true;
};
return false;
};
const check_characters = event => {
if(event.type == "keyup")
switch(event.keyCode){
case 27:
self.start_pause() || self.end_pause();
default:
break;
};
if(text_box.value == text_cache)
return;
const last_changed = text_cache.length == text_box.value.length,
characters = text_box.value.substr(text_cache.length - (last_changed ? 1 : 0)).split("");
text_cache = text_box.value;
if(game_status != "running")
return;
characters.forEach(own_character => {
allow_ignore_case && (own_character = own_character.toLowerCase());
if(!self.map[GAME].childs[WORDS].childs.some((word, i) => {
const ok = word && word.childs.some((character, j) => {
const ok = character && (allow_ignore_case ? character.text.toLowerCase() : character.text) == own_character;
if(ok){
with(self.map[GAME].childs[WORDS].childs[i].childs[j])
set_destruction(
self.map[GAME].childs[WORDS].childs[i].x + x,
self.map[GAME].childs[WORDS].childs[i].y + y
);
self.map[GAME].childs[WORDS].childs[i].childs[j] = null;
set_score(character_ok_points * (allow_points_multiplier ? multiplier : 1));
multiplier += multiplier_value;
};
return ok;
});
if(ok && self.map[GAME].childs[WORDS].childs[i].childs.every(character => !character)){
self.map[GAME].childs[WORDS].childs[i] = null;
play_audios_from(ok_sounds);
increase_life();
set_score(word_ok_points);
};
return ok;
})){
if(!['`', '´', '^',, '¨'].includes(own_character)){
play_audios_from(scream_sounds);
reduce_life();
set_score(character_wrong_points);
multiplier = 1;
};
};
});
};
const play_audios_from_uri = (uri_data, volume, from, to) => {
const audio = new Audio(uri_data);
audio.load();
audio.currentTime = from;
audio.volume = volume;
audio.play();
setTimeout(() => audio.pause(), (to ? to - from : audio.duration) * 1000);
};
const play_audios_from = set => {
const sound = set.length ? set[Math.random() * set.length >> 0] : null,
[from, to] = sound.fragments ? sound.fragments[Math.random() * sound.fragments.length >> 0] : [0, 0];
if(!sound)
return;
if(sound.uri_data){
play_audios_from_uri(sound.uri_data, sound.volume, from, to);
return;
}else if(sound.loading)
return;
const ajax = new XMLHttpRequest();
sound.loading = true;
ajax.open("get", sound.sound, true);
ajax.responseType = "blob";
ajax.onload = () => {
if(ajax.readyState == 4){
ajax.loading = false;
ajax.status == 200 && play_audios_from_uri(sound.uri_data = URL.createObjectURL(ajax.response), sound.volume, from, to);
};
};
ajax.send(null);
};
const set_destruction = (x, y) => {
const i = Math.random() * destruction_animations.length >> 0,
m = self.map[GAME].childs[DESTRUCTIONS].childs.length,
half = (character_size / 2) - (destruction_size / 2);
let thread,
j = 0,
k = 0;
for(; j < m; j ++)
if(!self.map[GAME].childs[DESTRUCTIONS].childs[j])
break;
self.map[GAME].childs[DESTRUCTIONS].childs[j] = {
type : "image",
x : x + half,
y : y + half,
width : destruction_size,
height : destruction_size,
cut_x : destruction_animations[i].animation[0][0],
cut_y : destruction_animations[i].animation[0][1],
cut_width : destruction_animations[i].animation[0][2],
cut_height : destruction_animations[i].animation[0][3],
url : destruction_animations[i].sprite
};
play_audios_from(destruction_sounds);
thread = kanvas.threads_add(() => {
if(k < destruction_animations[i].animation.length){
const _k = k >> 0;
with(self.map[GAME].childs[DESTRUCTIONS].childs[j]){
cut_x = destruction_animations[i].animation[_k][0];
cut_y = destruction_animations[i].animation[_k][1];
cut_width = destruction_animations[i].animation[_k][2];
cut_height = destruction_animations[i].animation[_k][3];
};
k += destruction_animations[i].frames_per_second / kanvas.get_real_fps();
}else{
kanvas.threads_remove(thread);
self.map[GAME].childs[DESTRUCTIONS].childs[j] = null;
};
});
};
const end_game = () => {
game_status = "ended";
game_working = false;
scores.build_add();
};
construct();
};