Files
monkeygg2.github.io/games/conways-game-of-life/main.js
T
2023-08-25 13:31:04 +05:30

1756 lines
49 KiB
JavaScript

/*
* TODO:
* - remember settings in the hash or offer link
* - life 1.05 is currently broken
* - better mobile handling: allow drawing
* - jump to coordinate
* - make screenshots, maybe gifs
* - allow people to upload patterns
* - maybe more than 2 states (non-life)
* - fail-safe http requests and pattern parsing
* - restore meta life
* - error when zooming while pattern is loading
* - run http://copy.sh/life/?pattern=demonoid_synth without crashing (improve memory efficiency)
* - some patterns break randomly (hard to reproduce, probably related to speed changing)
*/
"use strict";
var
/** @const */
DEFAULT_BORDER = 0.25,
/** @const */
DEFAULT_FPS = 20;
(function()
{
//var console = console || { log : function() {} };
var initial_title = document.title;
var initial_description = "";
if(!document.addEventListener)
{
// IE 8 seems to switch into rage mode if the code is only loaded partly,
// so we are saying goodbye earlier
return;
}
var
/**
* which pattern file is currently loaded
* @type {{title: String, urls, comment, view_url, source_url}}
* */
current_pattern,
// functions which is called when the pattern stops running
/** @type {function()|undefined} */
onstop,
last_mouse_x,
last_mouse_y,
mouse_set,
// is the game running ?
/** @type {boolean} */
running = false,
/** @type {number} */
max_fps,
// has the pattern list been loaded
/** @type {boolean} */
patterns_loaded = false,
/**
* path to the folder with all patterns
* @const
*/
pattern_path = "examples/",
loaded = false,
life = new LifeUniverse(),
drawer = new LifeCanvasDrawer(),
// example setups which are run at startup
// loaded from examples/
/** @type {Array.<string>} */
examples = (
"turingmachine,Turing Machine|gunstar,Gunstar|hacksaw,Hacksaw|tetheredrake,Tethered rake|" +
"primer,Primer|infinitegliderhotel,Infinite glider hotel|" +
"p94s,P94S|breeder1,Breeder 1|tlogtgrowth,tlog(t) growth|" +
"logt2growth,Log(t)^2 growth|infinitelwsshotel,Infinite LWSS hotel|c5greyship,c/5 greyship"
).split("|");
/** @type {function(function())} */
var nextFrame =
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
setTimeout;
// setup
window.onload = function()
{
if(loaded)
{
// onload has been called already
return;
}
loaded = true;
initial_description = document.querySelector("meta[name=description]").content;
if(!drawer.init(document.body))
{
set_text($("notice").getElementsByTagName("h4")[0],
"Canvas-less browsers are not supported. I'm sorry for that.");
return;
}
init_ui();
drawer.set_size(window.innerWidth, document.body.offsetHeight);
reset_settings();
// This gets called, when a pattern is loaded.
// It has to be called at least once before anything can happen.
// Since we always load a pattern, it's not necessary at this point.
//life.clear_pattern();
// production setup
// loads a pattern defined by ?pattern=filename (without extension)
// or a random small pattern instead
var query = location.search.substr(1).split("&"),
param,
parameters = {};
for(var i = 0; i < query.length; i++)
{
param = query[i].split("=");
parameters[param[0]] = param[1];
}
if(parameters["step"] && /^\d+$/.test(parameters["step"]))
{
var step_parameter = Math.round(Math.log(Number(parameters["step"])) / Math.LN2);
life.set_step(step_parameter);
}
let pattern_parameter = parameters["pattern"];
let pattern_parameter_looks_good = pattern_parameter && /^[a-z0-9_\.\-]+$/i.test(pattern_parameter);
let gist = parameters["gist"];
if(gist && /^[a-fA-F0-9]+$/.test(gist))
{
show_overlay("loading_popup");
let callback_name = "finish_load_gist" + (2147483647 * Math.random() | 0);
let jsonp_url = "https://api.github.com/gists/" + gist + "?callback=" + callback_name;
window[callback_name] = function(result)
{
let files = result["data"]["files"];
if(files)
{
for(let filename of Object.keys(files))
{
let file = files[filename];
let direct_url = file["raw_url"];
let view_url = "https://copy.sh/life/?gist=" + gist;
setup_pattern(file["content"], undefined, direct_url, view_url, filename);
}
}
else
{
if(pattern_parameter_looks_good)
{
try_load_pattern(pattern_parameter);
}
else
{
load_random();
}
}
};
let script = document.createElement("script");
script.src = jsonp_url;
document.getElementsByTagName("head")[0].appendChild(script);
}
else if(pattern_parameter_looks_good)
{
if(parameters["meta"] === "1")
{
try_load_meta();
}
else
{
// a pattern name has been given as a parameter
// try to load it, fallback to random pattern
try_load_pattern(pattern_parameter);
}
}
else
{
load_random();
}
if(parameters["noui"] === "1")
{
var elements = [
"statusbar", "about_button", "examples_menu",
"import_button", "settings_button", "zoomout_button",
"zoomin_button", "clear_button", "superstep_button",
"step_button", "rewind_button"
];
for(var i = 0; i < elements.length; i++)
{
$(elements[i]).style.display = "none";
}
}
if(parameters["fps"] && /^\d+$/.test(parameters["fps"]))
{
max_fps = +parameters["fps"];
}
function try_load_meta()
{
// loading metapixels is broken now, keep this for later
load_random();
/*
var otca_on, otca_off, otca_pattern;
show_overlay("loading_popup");
http_get_multiple([
{
url : pattern_path + "otcametapixel.rle",
onready : function(result)
{
var field = formats.parse_rle(result).field;
life.move_field(field, -5, -5);
life.setup_field(field);
otca_on = life.root.se.nw;
}
},
{
url : pattern_path + "otcametapixeloff.rle",
onready : function(result)
{
var field = formats.parse_rle(result).field;
life.move_field(field, -5, -5);
life.setup_field(field);
otca_off = life.root.se.nw;
}
},
{
url : pattern_path + pattern_parameter + ".rle",
onready : function(result)
{
otca_pattern = formats.parse_rle(result).field;
}
}
],
function()
{
load_otca(otca_on, otca_off, otca_pattern);
},
function()
{
// fallback to random pattern
load_random();
});
*/
}
function try_load_pattern(id)
{
show_overlay("loading_popup");
http_get(
rle_link(id),
function(text)
{
//console.profile("main setup");
setup_pattern(text, pattern_parameter);
//console.profileEnd("main setup");
},
function()
{
load_random();
}
);
}
function load_random()
{
var random_pattern = examples[Math.random() * examples.length | 0].split(",")[0];
show_overlay("loading_popup");
http_get(
rle_link(random_pattern),
function(text) {
setup_pattern(text, random_pattern);
}
);
}
function init_ui()
{
$("about_close").style.display = "inline";
hide_element($("notice"));
hide_overlay();
show_element($("toolbar"));
show_element($("statusbar"));
show_element($("about_main"));
var style_element = document.createElement("style");
document.head.appendChild(style_element);
window.onresize = debounce(function()
{
drawer.set_size(window.innerWidth, document.body.offsetHeight);
requestAnimationFrame(lazy_redraw.bind(0, life.root));
}, 500);
$("gen_step").onchange = function(e)
{
if(this.type === "number")
{
var value = Number(this.value);
if(!value)
{
return;
}
var closest_pow2 = Math.pow(2, Math.round(Math.log(value) / Math.LN2));
if(value <= 1)
{
this.value = 1;
}
else
{
this.value = closest_pow2;
}
this.step = this.value / 2;
}
};
$("run_button").onclick = function()
{
if(running)
{
stop();
}
else
{
run();
}
};
$("step_button").onclick = function()
{
if(!running)
{
step(true);
}
};
$("superstep_button").onclick = function()
{
if(!running)
{
step(false);
}
};
$("clear_button").onclick = function()
{
stop(function()
{
set_title();
set_text($("pattern_name"), "");
set_query("");
life.clear_pattern();
update_hud();
drawer.center_view();
drawer.redraw(life.root);
});
};
$("rewind_button").onclick = function()
{
if(life.rewind_state)
{
stop(function()
{
life.restore_rewind_state();
fit_pattern();
drawer.redraw(life.root);
update_hud();
});
}
};
//drawer.canvas.ondblclick = function(e)
//{
// drawer.zoom_at(false, e.clientX, e.clientY);
// update_hud();
// lazy_redraw(life.root);
// return false;
//};
drawer.canvas.onmousedown = function(e)
{
if(e.which === 3 || e.which === 2)
{
if(drawer.cell_width >= 1) // only at reasonable zoom levels
{
var coords = drawer.pixel2cell(e.clientX, e.clientY);
mouse_set = !life.get_bit(coords.x, coords.y);
window.addEventListener("mousemove", do_field_draw, true);
do_field_draw(e);
}
}
else if(e.which === 1)
{
last_mouse_x = e.clientX;
last_mouse_y = e.clientY;
//console.log("start", e.clientX, e.clientY);
window.addEventListener("mousemove", do_field_move, true);
(function redraw()
{
if(last_mouse_x !== null)
{
requestAnimationFrame(redraw);
}
lazy_redraw(life.root);
})();
}
return false;
};
var scaling = false;
var last_distance = 0;
function distance(touches)
{
console.assert(touches.length >= 2);
return Math.sqrt(
(touches[0].clientX-touches[1].clientX) * (touches[0].clientX-touches[1].clientX) +
(touches[0].clientY-touches[1].clientY) * (touches[0].clientY-touches[1].clientY));
}
drawer.canvas.addEventListener("touchstart", function(e)
{
if(e.touches.length === 2)
{
scaling = true;
last_distance = distance(e.touches);
e.preventDefault();
}
else if(e.touches.length === 1)
{
// left mouse simulation
var ev = {
which: 1,
clientX: e.changedTouches[0].clientX,
clientY: e.changedTouches[0].clientY,
};
drawer.canvas.onmousedown(ev);
e.preventDefault();
}
}, false);
drawer.canvas.addEventListener("touchmove", function(e)
{
if(scaling)
{
let new_distance = distance(e.touches);
let changed = false;
const MIN_DISTANCE = 50;
while(last_distance - new_distance > MIN_DISTANCE)
{
last_distance -= MIN_DISTANCE;
drawer.zoom_centered(true);
changed = true;
}
while(last_distance - new_distance < -MIN_DISTANCE)
{
last_distance += MIN_DISTANCE;
drawer.zoom_centered(false);
changed = true;
}
if(changed)
{
update_hud();
lazy_redraw(life.root);
}
}
else
{
var ev = {
clientX: e.changedTouches[0].clientX,
clientY: e.changedTouches[0].clientY,
};
do_field_move(ev);
e.preventDefault();
}
}, false);
drawer.canvas.addEventListener("touchend", function(e)
{
window.onmouseup(e);
e.preventDefault();
scaling = false;
}, false);
drawer.canvas.addEventListener("touchcancel", function(e)
{
window.onmouseup(e);
e.preventDefault();
scaling = false;
}, false);
window.onmouseup = function(e)
{
last_mouse_x = null;
last_mouse_y = null;
window.removeEventListener("mousemove", do_field_draw, true);
window.removeEventListener("mousemove", do_field_move, true);
};
window.onmousemove = function(e)
{
var coords = drawer.pixel2cell(e.clientX, e.clientY);
set_text($("label_mou"), coords.x + ", " + coords.y);
fix_width($("label_mou"));
};
drawer.canvas.oncontextmenu = function(e)
{
return false;
};
drawer.canvas.onmousewheel = function(e)
{
e.preventDefault();
drawer.zoom_at((e.wheelDelta || -e.detail) < 0, e.clientX, e.clientY);
update_hud();
lazy_redraw(life.root);
return false;
};
drawer.canvas.addEventListener("DOMMouseScroll", drawer.canvas.onmousewheel, false);
window.onkeydown = function(e)
{
var chr = e.which,
do_redraw = false,
target = e.target.nodeName;
//console.log(e.target)
//console.log(chr + " " + e.charCode + " " + e.keyCode);
if(target === "INPUT" || target === "TEXTAREA")
{
return true;
}
if(e.ctrlKey || e.shiftKey || e.altKey)
{
return true;
}
if(chr === 37 || chr === 72)
{
drawer.move(15, 0);
do_redraw = true;
}
else if(chr === 38 || chr === 75)
{
drawer.move(0, 15);
do_redraw = true;
}
else if(chr === 39 || chr === 76)
{
drawer.move(-15, 0);
do_redraw = true;
}
else if(chr === 40 || chr === 74)
{
drawer.move(0, -15);
do_redraw = true;
}
else if(chr === 27)
{
// escape
hide_overlay();
return false;
}
else if(chr === 13)
{
// enter
$("run_button").onclick();
return false;
}
else if(chr === 32)
{
// space
$("step_button").onclick();
return false;
}
else if(chr === 9)
{
$("superstep_button").onclick();
return false;
}
else if(chr === 189 || chr === 173 || chr === 109)
{
// -
drawer.zoom_centered(true);
do_redraw = true;
}
else if(chr === 187 || chr === 61)
{
// + and =
drawer.zoom_centered(false);
do_redraw = true;
}
else if(chr === 8)
{
// backspace
$("rewind_button").onclick();
return false;
}
else if(chr === 219 || chr === 221)
{
// [ ]
var step = life.step;
if(chr === 219)
step--;
else
step++;
if(step >= 0)
{
life.set_step(step);
set_text($("label_step"), Math.pow(2, step));
}
return false;
}
if(do_redraw)
{
lazy_redraw(life.root);
return false;
}
return true;
};
$("faster_button").onclick = function()
{
var step = life.step + 1;
life.set_step(step);
set_text($("label_step"), Math.pow(2, step));
};
$("slower_button").onclick = function()
{
if(life.step > 0)
{
var step = life.step - 1;
life.set_step(step);
set_text($("label_step"), Math.pow(2, step));
}
};
$("normalspeed_button").onclick = function()
{
life.set_step(0);
set_text($("label_step"), 1);
};
$("zoomin_button").onclick = function()
{
drawer.zoom_centered(false);
update_hud();
lazy_redraw(life.root);
};
$("zoomout_button").onclick = function()
{
drawer.zoom_centered(true);
update_hud();
lazy_redraw(life.root);
};
$("initial_pos_button").onclick = function()
{
fit_pattern();
lazy_redraw(life.root);
update_hud();
};
$("middle_button").onclick = function()
{
drawer.center_view();
lazy_redraw(life.root);
};
var positions = [
["ne", 1, -1],
["nw", -1, -1],
["se", 1, 1],
["sw", -1, 1],
["n", 0, -1],
["e", -1, 0],
["s", 0, 1],
["w", 1, 0],
];
for(var i = 0; i < positions.length; i++)
{
var node = document.getElementById(positions[i][0] + "_button");
node.onclick = (function(info)
{
return function()
{
drawer.move(info[1] * -30, info[2] * -30);
lazy_redraw(life.root);
};
})(positions[i]);
}
var select_rules = $("select_rules").getElementsByTagName("span");
for(var i = 0; i < select_rules.length; i++)
{
/** @this {Element} */
select_rules[i].onclick = function()
{
$("rule").value = this.getAttribute("data-rule");
};
}
$("import_submit").onclick = function()
{
var previous = current_pattern && current_pattern.title;
var files = $("import_file").files;
function load(text)
{
setup_pattern(text, undefined);
if(previous !== current_pattern.title) {
show_alert(current_pattern);
$("import_file").value = "";
}
}
if(files && files.length)
{
let filereader = new FileReader();
filereader.onload = function()
{
load(filereader.result);
};
filereader.readAsText(files[0]);
}
else
{
load($("import_text").value);
}
};
$("import_abort").onclick = function()
{
hide_overlay();
};
$("import_button").onclick = function()
{
show_overlay("import_dialog");
$("import_text").value = "";
set_text($("import_info"), "");
};
$("export_button").onclick = function()
{
const rle = formats.generate_rle(life, undefined, ["Generated by copy.sh/life"]);
download(rle, "pattern.rle");
};
$("randomize_button").onclick = function()
{
$("randomize_density").value = 0.5;
$("randomize_width").value = 200;
$("randomize_height").value = 200;
show_overlay("randomize_dialog");
};
$("randomize_submit").onclick = function()
{
const density = Math.max(0, Math.min(1, +$("randomize_density").value)) || 0.5;
const width = Math.max(0, +$("randomize_width").value) || 200;
const height = Math.max(0, +$("randomize_height").value) || 200;
stop(function()
{
life.clear_pattern();
// Note: Not exact density because some points may be repeated
const field_x = new Int32Array(Math.round(width * height * density));
const field_y = new Int32Array(field_x.length);
for(let i = 0; i < field_x.length; i++) {
field_x[i] = Math.random() * width;
field_y[i] = Math.random() * height;
}
var bounds = life.get_bounds(field_x, field_y);
life.make_center(field_x, field_y, bounds);
life.setup_field(field_x, field_y, bounds);
life.save_rewind_state();
hide_overlay();
fit_pattern();
lazy_redraw(life.root);
update_hud();
set_text($("pattern_name"), "Random pattern");
set_title("Random pattern");
current_pattern = {
title : "Random pattern",
comment : "",
};
});
};
$("settings_submit").onclick = function()
{
var new_rule_s,
new_rule_b,
new_gen_step;
hide_overlay();
new_rule_s = formats.parse_rule($("rule").value, true);
new_rule_b = formats.parse_rule($("rule").value, false);
new_gen_step = Math.round(Math.log(Number($("gen_step").value) || 0) / Math.LN2);
life.set_rules(new_rule_s, new_rule_b);
if(!new_gen_step || new_gen_step < 0) {
life.set_step(0);
set_text($("label_step"), "1");
}
else {
life.set_step(new_gen_step);
set_text($("label_step"), Math.pow(2, new_gen_step));
}
max_fps = Number($("max_fps").value);
if(!max_fps || max_fps < 0) {
max_fps = DEFAULT_FPS;
}
drawer.border_width = parseFloat($("border_width").value);
if(isNaN(drawer.border_width) || drawer.border_width < 0 || drawer.border_width > .5)
{
drawer.border_width = DEFAULT_BORDER;
}
//drawer.cell_color = validate_color($("cell_color").value) || "#ccc";
//drawer.background_color = validate_color($("background_color").value) || "#000";
var style_text = document.createTextNode(
".button,.menu>div{background-color:" + drawer.cell_color +
";box-shadow:2px 2px 4px " + drawer.cell_color + "}" +
"#statusbar>div{border-color:" + drawer.cell_color + "}"
);
style_element.appendChild(style_text);
$("pattern_name").style.color =
$("statusbar").style.color = drawer.cell_color;
$("statusbar").style.textShadow = "0px 0px 1px " + drawer.cell_color;
$("toolbar").style.color = drawer.background_color;
lazy_redraw(life.root);
};
$("settings_reset").onclick = function()
{
reset_settings();
lazy_redraw(life.root);
hide_overlay();
};
$("settings_button").onclick = function()
{
show_overlay("settings_dialog");
$("rule").value = formats.rule2str(life.rule_s, life.rule_b);
$("max_fps").value = max_fps;
$("gen_step").value = Math.pow(2, life.step);
$("border_width").value = drawer.border_width;
//$("cell_color").value = drawer.cell_color;
//$("background_color").value = drawer.background_color;
};
$("settings_abort").onclick =
$("pattern_close").onclick =
$("alert_close").onclick =
$("randomize_abort").onclick =
$("about_close").onclick = function()
{
hide_overlay();
};
$("pattern_name").onclick = function()
{
show_alert(current_pattern);
};
$("about_button").onclick = function()
{
show_overlay("about");
};
//$("more_button").onclick = show_pattern_chooser;
$("pattern_button").onclick = show_pattern_chooser;
function show_pattern_chooser()
{
if(patterns_loaded)
{
show_overlay("pattern_chooser");
return;
}
patterns_loaded = true;
if(false)
{
var frame = document.createElement("iframe");
frame.src = "examples/";
frame.id = "example_frame";
$("pattern_list").appendChild(frame);
show_overlay("pattern_chooser");
window["load_pattern"] = function(id)
{
show_overlay("loading_popup");
http_get(rle_link(id), function(text)
{
setup_pattern(text, id);
set_query(id);
show_alert(current_pattern);
life.set_step(0);
set_text($("label_step"), "1");
});
};
}
else
{
patterns_loaded = true;
show_overlay("loading_popup");
http_get(pattern_path + "list", function(text)
{
var patterns = text.split("\n"),
list = $("pattern_list");
show_overlay("pattern_chooser");
patterns.forEach(function(pattern)
{
var
name = pattern.split(" ")[0],
size = pattern.split(" ")[1],
name_element = document.createElement("div"),
size_element = document.createElement("span");
set_text(name_element, name);
set_text(size_element, size);
size_element.className = "size";
name_element.appendChild(size_element);
list.appendChild(name_element);
name_element.onclick = function()
{
show_overlay("loading_popup");
http_get(rle_link(name), function(text)
{
setup_pattern(text, name);
set_query(name);
show_alert(current_pattern);
life.set_step(0);
set_text($("label_step"), "1");
});
};
});
});
}
}
if(false)
{
var examples_menu = $("examples_menu");
examples.forEach(function(example)
{
var file = example.split(",")[0],
name = example.split(",")[1],
menu = document.createElement("div");
set_text(menu, name);
menu.onclick = function()
{
show_overlay("loading_popup");
http_get(rle_link(file), function(text)
{
setup_pattern(text, file);
set_query(file);
show_alert(current_pattern);
});
};
examples_menu.appendChild(menu);
});
}
}
};
document.addEventListener("DOMContentLoaded", window.onload, false);
/** @param {*=} absolute */
function rle_link(id, absolute)
{
if(!id.endsWith(".mc"))
{
id = id + ".rle";
}
if(!absolute || location.hostname === "localhost")
{
return pattern_path + id;
}
else
{
let protocol = location.protocol === "http:" ? "http:" : "https:";
return protocol + "//copy.sh/life/" + pattern_path + id;
}
}
function view_link(id)
{
let protocol = location.protocol === "http:" ? "http:" : "https:";
return protocol + "//copy.sh/life/?pattern=" + id;
}
/**
* @param {function()=} callback
*/
function stop(callback)
{
if(running)
{
running = false;
set_text($("run_button"), "Run");
onstop = callback;
}
else
{
if(callback) {
callback();
}
}
}
function reset_settings()
{
drawer.background_color = "#000000";
drawer.cell_color = "#cccccc";
drawer.border_width = DEFAULT_BORDER;
drawer.cell_width = 2;
life.rule_b = 1 << 3;
life.rule_s = 1 << 2 | 1 << 3;
life.set_step(0);
set_text($("label_step"), "1");
max_fps = DEFAULT_FPS;
set_text($("label_zoom"), "1:2");
fix_width($("label_mou"));
drawer.center_view();
}
/**
* @param {string=} pattern_source_url
* @param {string=} view_url
* @param {string=} title
*/
function setup_pattern(pattern_text, pattern_id, pattern_source_url, view_url, title)
{
const is_mc = pattern_text.startsWith("[M2]");
if(!is_mc)
{
var result = formats.parse_pattern(pattern_text.trim());
if(result.error)
{
set_text($("import_info"), result.error);
return;
}
}
else
{
result = {
comment: "",
urls: [],
short_comment: "",
};
}
stop(function()
{
if(title && !result.title)
{
result.title = title;
}
if(pattern_id && !result.title)
{
result.title = pattern_id;
}
life.clear_pattern();
if(!is_mc)
{
var bounds = life.get_bounds(result.field_x, result.field_y);
life.make_center(result.field_x, result.field_y, bounds);
life.setup_field(result.field_x, result.field_y, bounds);
}
else
{
result = load_macrocell(life, pattern_text);
const step = 15;
life.set_step(step);
set_text($("label_step"), Math.pow(2, step));
}
life.save_rewind_state();
if(result.rule_s && result.rule_b)
{
life.set_rules(result.rule_s, result.rule_b);
}
else
{
life.set_rules(1 << 2 | 1 << 3, 1 << 3);
}
hide_overlay();
fit_pattern();
drawer.redraw(life.root);
update_hud();
set_text($("pattern_name"), result.title || "no name");
set_title(result.title);
document.querySelector("meta[name=description]").content =
result.comment.replace(/\n/g, " - ") + " - " + initial_description;
if(!pattern_source_url && pattern_id)
{
pattern_source_url = rle_link(pattern_id, true);
}
if(!view_url && pattern_id)
{
view_url = view_link(pattern_id);
}
current_pattern = {
title : result.title,
comment : result.comment,
urls : result.urls,
view_url : view_url,
source_url: pattern_source_url,
};
});
}
function fit_pattern()
{
var bounds = life.get_root_bounds();
drawer.fit_bounds(bounds);
}
/*
* load a pattern consisting of otca metapixels
*/
/*function load_otca(otca_on, otca_off, field)
{
var bounds = life.get_bounds(field);
life.set_step(10);
max_fps = 6;
drawer.cell_width = 1 / 32;
life.make_center(field, bounds);
life.setup_meta(otca_on, otca_off, field, bounds);
update_hud();
drawer.redraw(life.root);
}*/
function run()
{
var n = 0,
start,
last_frame,
frame_time = 1000 / max_fps,
interval,
per_frame = frame_time;
set_text($("run_button"), "Stop");
running = true;
if(life.generation === 0)
{
life.save_rewind_state();
}
interval = setInterval(function()
{
update_hud(1000 / frame_time);
}, 666);
start = Date.now();
last_frame = start - per_frame;
function update()
{
if(!running)
{
clearInterval(interval);
update_hud(1000 / frame_time);
if(onstop) {
onstop();
}
return;
}
var time = Date.now();
if(per_frame * n < (time - start))
{
life.next_generation(true);
drawer.redraw(life.root);
n++;
// readability ... my ass
frame_time += (-last_frame - frame_time + (last_frame = time)) / 15;
if(frame_time < .7 * per_frame)
{
n = 1;
start = Date.now();
}
}
nextFrame(update);
}
update();
}
function step(is_single)
{
var time = Date.now();
if(life.generation === 0)
{
life.save_rewind_state();
}
life.next_generation(is_single);
drawer.redraw(life.root);
update_hud(1000 / (Date.now() - time));
if(time < 3)
{
set_text($("label_fps"), "> 9000");
}
}
function show_alert(pattern)
{
if(pattern.title || pattern.comment || pattern.urls.length)
{
show_overlay("alert");
set_text($("pattern_title"), pattern.title || "");
set_text($("pattern_description"), pattern.comment || "");
$("pattern_urls").innerHTML = "";
for(let url of pattern.urls)
{
let a = document.createElement("a");
a.href = url;
a.textContent = url;
a.target = "_blank";
$("pattern_urls").appendChild(a);
$("pattern_urls").appendChild(document.createElement("br"));
}
if(pattern.view_url)
{
show_element($("pattern_link_container"));
set_text($("pattern_link"), pattern.view_url);
$("pattern_link").href = pattern.view_url;
}
else
{
hide_element($("pattern_link_container"));
}
if(pattern.source_url)
{
show_element($("pattern_file_container"));
set_text($("pattern_file_link"), pattern.source_url);
$("pattern_file_link").href = pattern.source_url;
}
else
{
hide_element($("pattern_file_container"));
}
}
}
function show_overlay(overlay_id)
{
show_element($("overlay"));
// allow scroll bars when overlay is visible
document.body.style.overflow = "auto";
var overlays = $("overlay").children;
for(var i = 0; i < overlays.length; i++)
{
var child = overlays[i];
if(child.id === overlay_id)
{
show_element(child);
}
else
{
hide_element(child);
}
}
}
function hide_overlay()
{
hide_element($("overlay"));
document.body.style.overflow = "hidden";
}
/**
* @param {number=} fps
*/
function update_hud(fps)
{
if(fps) {
set_text($("label_fps"), fps.toFixed(1));
}
set_text($("label_gen"), format_thousands(life.generation, "\u202f"));
fix_width($("label_gen"));
set_text($("label_pop"), format_thousands(life.root.population, "\u202f"));
fix_width($("label_pop"));
if(drawer.cell_width >= 1)
{
set_text($("label_zoom"), "1:" + drawer.cell_width);
}
else
{
set_text($("label_zoom"), 1 / drawer.cell_width + ":1");
}
}
function lazy_redraw(node)
{
if(!running || max_fps < 15)
{
drawer.redraw(node);
}
}
function set_text(obj, text)
{
obj.textContent = String(text);
}
/**
* fixes the width of an element to its current size
*/
function fix_width(element)
{
element.style.padding = "0";
element.style.width = "";
if(!element.last_width || element.last_width < element.offsetWidth) {
element.last_width = element.offsetWidth;
}
element.style.padding = "";
element.style.width = element.last_width + "px";
}
function validate_color(color_str)
{
return /^#(?:[a-f0-9]{3}|[a-f0-9]{6})$/i.test(color_str) ? color_str : false;
}
/**
* @param {function(string,number)=} onerror
*/
function http_get(url, onready, onerror)
{
var http = new XMLHttpRequest();
http.onreadystatechange = function()
{
if(http.readyState === 4)
{
if(http.status === 200)
{
onready(http.responseText, url);
}
else
{
if(onerror)
{
onerror(http.responseText, http.status);
}
}
}
};
http.open("get", url, true);
http.send("");
return {
cancel : function()
{
http.abort();
}
};
}
function http_get_multiple(urls, ondone, onerror)
{
var count = urls.length,
done = 0,
error = false,
handlers;
handlers = urls.map(function(url)
{
return http_get(
url.url,
function(result)
{
// a single request was successful
if(error) {
return;
}
if(url.onready) {
url.onready(result);
}
done++;
if(done === count) {
ondone();
}
},
function(result, status_code)
{
// a single request has errored
if(!error)
{
error = true;
onerror();
for(var i = 0; i < handlers.length; i++)
{
handlers[i].cancel();
}
}
}
);
});
}
/*
* The mousemove event which allows moving around
*/
function do_field_move(e)
{
if(last_mouse_x !== null)
{
let dx = Math.round(e.clientX - last_mouse_x);
let dy = Math.round(e.clientY - last_mouse_y);
drawer.move(dx, dy);
//lazy_redraw(life.root);
last_mouse_x += dx;
last_mouse_y += dy;
}
}
/*
* The mousemove event which draw pixels
*/
function do_field_draw(e)
{
var coords = drawer.pixel2cell(e.clientX, e.clientY);
// don't draw the same pixel twice
if(coords.x !== last_mouse_x || coords.y !== last_mouse_y)
{
life.set_bit(coords.x, coords.y, mouse_set);
update_hud();
drawer.draw_cell(coords.x, coords.y, mouse_set);
last_mouse_x = coords.x;
last_mouse_y = coords.y;
}
}
function $(id)
{
return document.getElementById(id);
}
function set_query(filename)
{
if(!window.history.replaceState)
{
return;
}
if(filename)
{
window.history.replaceState(null, "", "?pattern=" + filename);
}
else
{
window.history.replaceState(null, "", "/life/");
}
}
/** @param {string=} title */
function set_title(title)
{
if(title)
{
document.title = title + " - " + initial_title;
}
else
{
document.title = initial_title;
}
}
function hide_element(node)
{
node.style.display = "none";
}
function show_element(node)
{
node.style.display = "block";
}
function pad0(str, n)
{
while(str.length < n)
{
str = "0" + str;
}
return str;
}
// Put sep as a seperator into the thousands spaces of and Integer n
// Doesn't handle numbers >= 10^21
function format_thousands(n, sep)
{
if(n < 0)
{
return "-" + format_thousands(-n, sep);
}
if(isNaN(n) || !isFinite(n) || n >= 1e21)
{
return n + "";
}
function format(str)
{
if(str.length < 3)
{
return str;
}
else
{
return format(str.slice(0, -3)) + sep + str.slice(-3);
}
}
return format(n + "");
}
function debounce(func, timeout)
{
var timeout_id;
return function()
{
var me = this,
args = arguments;
clearTimeout(timeout_id);
timeout_id = setTimeout(function()
{
func.apply(me, Array.prototype.slice.call(args));
}, timeout);
};
}
function download(text, name)
{
var a = document.createElement("a");
a["download"] = name;
a.href = window.URL.createObjectURL(new Blob([text]));
a.dataset["downloadurl"] = ["text/plain", a["download"], a.href].join(":");
if(document.createEvent)
{
var ev = document.createEvent("MouseEvent");
ev.initMouseEvent("click", true, true, window,
0, 0, 0, 0, 0, false, false, false, false, 0, null);
a.dispatchEvent(ev);
}
else
{
a.click();
}
window.URL.revokeObjectURL(a.href);
}
})();