ts-hashlife
Version:
Efficient TypeScript implementation of HashLife, an optimized algorithm for simulating Conway's Game of Life with memoization and quadtree-based compression.
609 lines • 23 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LifeUniverse = exports.TreeNode = void 0;
const event_bus_1 = __importDefault(require("./event-bus"));
const LOAD_FACTOR = 0.9;
const INITIAL_SIZE = 16;
const HASHMAP_LIMIT = 24;
const MASK_LEFT = 1;
const MASK_TOP = 2;
const MASK_RIGHT = 4;
const MASK_BOTTOM = 8;
class TreeNode {
constructor(nw, ne, sw, se, id, level, population) {
this.nw = nw || null;
this.ne = ne || null;
this.sw = sw || null;
this.se = se || null;
this.id = id;
if (level !== undefined && population !== undefined) {
this.level = level;
this.population = population;
}
else if (nw !== null && nw.level !== undefined) {
this.level = nw.level + 1;
this.population =
(nw.population || 0) +
(ne.population || 0) +
(sw.population || 0) +
(se.population || 0);
}
else {
// Leaf node
this.level = 0;
this.population = 0;
}
this.cache = null;
this.quick_cache = null;
this.hashmap_next = undefined;
}
}
exports.TreeNode = TreeNode;
class LifeUniverse {
constructor() {
this._step = 0;
this._generation = 0;
this.last_id = 0;
this.hashmap_size = 0;
this.max_load = 0;
this.hashmap = [];
this.empty_tree_cache = [];
this.level2_cache = [];
this._powers = new Float64Array(1024);
this._powers[0] = 1;
for (let i = 1; i < 1024; i++) {
this._powers[i] = this._powers[i - 1] * 2;
}
this._bitcounts = new Int8Array(0x758);
this._bitcounts.set([0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4]);
for (let i = 0x10; i < 0x758; i++) {
this._bitcounts[i] =
this._bitcounts[i & 0xf] +
this._bitcounts[(i >> 4) & 0xf] +
this._bitcounts[i >> 8];
}
this.rule_b = 1 << 3;
this.rule_s = (1 << 2) | (1 << 3);
this.root = null;
this.rewind_state = null;
this.step = 0;
this.generation = 0;
this.false_leaf = new TreeNode(null, null, null, null, 3, 0, 0);
this.true_leaf = new TreeNode(null, null, null, null, 2, 0, 1);
this.clear_pattern();
}
get step() {
return this._step;
}
set step(value) {
this._step = value;
event_bus_1.default.emit("step", Math.pow(2, value));
}
get generation() {
return this._generation;
}
set generation(value) {
this._generation = value;
event_bus_1.default.emit("generation", value);
}
pow2(x) {
if (x >= 1024)
return Infinity;
return this._powers[x];
}
save_rewind_state() {
this.rewind_state = this.root;
}
restore_rewind_state() {
this.generation = 0;
this.root = this.rewind_state;
this.garbage_collect();
}
eval_mask(bitmask) {
const rule = bitmask & 32 ? this.rule_s : this.rule_b;
return (rule >> this._bitcounts[bitmask & 0x757]) & 1;
}
level1_create(bitmask) {
return this.create_tree(bitmask & 1 ? this.true_leaf : this.false_leaf, bitmask & 2 ? this.true_leaf : this.false_leaf, bitmask & 4 ? this.true_leaf : this.false_leaf, bitmask & 8 ? this.true_leaf : this.false_leaf);
}
set_bit(x, y, living) {
const level = this.get_level_from_bounds({ x: x, y: y });
if (this.root === null) {
this.root = this.empty_tree(level);
}
if (living) {
while (level > this.root.level) {
this.root = this.expand_universe(this.root);
}
}
else {
if (level > this.root.level) {
return;
}
}
this.root = this.node_set_bit(this.root, x, y, living);
}
get_bit(x, y) {
const level = this.get_level_from_bounds({ x: x, y: y });
if (this.root === null || level > this.root.level) {
return false;
}
else {
return this.node_get_bit(this.root, x, y);
}
}
get_root_bounds() {
if (this.root === null || this.root.population === 0) {
return {
top: 0,
left: 0,
bottom: 0,
right: 0,
};
}
const bounds = {
top: Infinity,
left: Infinity,
bottom: -Infinity,
right: -Infinity,
}, offset = this.pow2(this.root.level - 1);
this.node_get_boundary(this.root, -offset, -offset, MASK_TOP | MASK_LEFT | MASK_BOTTOM | MASK_RIGHT, bounds);
return bounds;
}
empty_tree(level) {
if (this.empty_tree_cache[level]) {
return this.empty_tree_cache[level];
}
let t;
if (level === 1) {
t = this.false_leaf;
}
else {
t = this.empty_tree(level - 1);
}
return (this.empty_tree_cache[level] = this.create_tree(t, t, t, t));
}
expand_universe(node) {
const t = this.empty_tree(node.level - 1);
return this.create_tree(this.create_tree(t, t, t, node.nw), this.create_tree(t, t, node.ne, t), this.create_tree(t, node.sw, t, t), this.create_tree(node.se, t, t, t));
}
uncache(also_quick) {
for (let i = 0; i <= this.hashmap_size; i++) {
const node = this.hashmap[i];
if (node !== undefined) {
node.cache = null;
node.hashmap_next = undefined;
if (also_quick)
node.quick_cache = null;
}
}
}
in_hashmap(n) {
const hash = this.calc_hash(n.nw.id, n.ne.id, n.sw.id, n.se.id) &
this.hashmap_size;
let node = this.hashmap[hash];
while (true) {
if (node === undefined) {
return false;
}
else if (node === n) {
return true;
}
node = node.hashmap_next;
}
}
hashmap_insert(n) {
const hash = this.calc_hash(n.nw.id, n.ne.id, n.sw.id, n.se.id) &
this.hashmap_size;
let node = this.hashmap[hash];
let prev;
while (true) {
if (node === undefined) {
if (prev !== undefined) {
prev.hashmap_next = n;
}
else {
this.hashmap[hash] = n;
}
return;
}
prev = node;
node = node.hashmap_next;
}
}
create_tree(nw, ne, sw, se) {
const hash = this.calc_hash(nw.id, ne.id, sw.id, se.id) & this.hashmap_size;
let node = this.hashmap[hash];
let prev;
while (true) {
if (node === undefined) {
if (this.last_id > this.max_load) {
this.garbage_collect();
return this.create_tree(nw, ne, sw, se);
}
const new_node = new TreeNode(nw, ne, sw, se, this.last_id++);
if (prev !== undefined) {
prev.hashmap_next = new_node;
}
else {
this.hashmap[hash] = new_node;
}
return new_node;
}
else if (node.nw === nw &&
node.ne === ne &&
node.sw === sw &&
node.se === se) {
return node;
}
prev = node;
node = node.hashmap_next;
}
}
next_generation(is_single) {
let root = this.root;
while ((is_single && root.level <= this.step + 2) ||
root.nw.population !== root.nw.se.se.population ||
root.ne.population !== root.ne.sw.sw.population ||
root.sw.population !== root.sw.ne.ne.population ||
root.se.population !== root.se.nw.nw.population) {
root = this.expand_universe(root);
}
if (is_single) {
this.generation += this.pow2(this.step);
root = this.node_next_generation(root);
}
else {
this.generation += this.pow2(this.root.level - 2);
root = this.node_quick_next_generation(root);
}
this.root = root;
}
garbage_collect() {
if (this.hashmap_size < (1 << HASHMAP_LIMIT) - 1) {
this.hashmap_size = (this.hashmap_size << 1) | 1;
this.hashmap = [];
}
this.max_load = (this.hashmap_size * LOAD_FACTOR) | 0;
for (let i = 0; i <= this.hashmap_size; i++)
this.hashmap[i] = undefined;
this.last_id = 4;
this.node_hash(this.root);
}
calc_hash(nw_id, ne_id, sw_id, se_id) {
return (((nw_id * 23) ^ ne_id) * 23) ^ (sw_id * 23) ^ se_id;
}
clear_pattern() {
this.last_id = 4;
this.hashmap_size = (1 << INITIAL_SIZE) - 1;
this.max_load = (this.hashmap_size * LOAD_FACTOR) | 0;
this.hashmap = [];
this.empty_tree_cache = [];
this.level2_cache = Array(0x10000);
for (let i = 0; i <= this.hashmap_size; i++)
this.hashmap[i] = undefined;
this.root = this.empty_tree(3);
this.generation = 0;
}
get_bounds(field_x, field_y) {
if (!field_x.length) {
return {
top: 0,
left: 0,
bottom: 0,
right: 0,
};
}
const bounds = {
top: field_y[0],
left: field_x[0],
bottom: field_y[0],
right: field_x[0],
};
const len = field_x.length;
for (let i = 1; i < len; i++) {
const x = field_x[i], y = field_y[i];
if (x < bounds.left) {
bounds.left = x;
}
else if (x > bounds.right) {
bounds.right = x;
}
if (y < bounds.top) {
bounds.top = y;
}
else if (y > bounds.bottom) {
bounds.bottom = y;
}
}
return bounds;
}
get_level_from_bounds(bounds) {
let max = 4;
const keys = Object.keys(bounds);
for (let i = 0; i < keys.length; i++) {
const coordinate = bounds[keys[i]];
if (coordinate + 1 > max) {
max = coordinate + 1;
}
else if (-coordinate > max) {
max = -coordinate;
}
}
return Math.ceil(Math.log(max) / Math.LN2) + 1;
}
make_center(field_x, field_y, bounds) {
const offset_x = Math.round((bounds.left - bounds.right) / 2) - bounds.left, offset_y = Math.round((bounds.top - bounds.bottom) / 2) - bounds.top;
this.move_field(field_x, field_y, offset_x, offset_y);
bounds.left += offset_x;
bounds.right += offset_x;
bounds.top += offset_y;
bounds.bottom += offset_y;
}
move_field(field_x, field_y, offset_x, offset_y) {
const len = field_x.length;
for (let i = 0; i < len; i++) {
field_x[i] += offset_x;
field_y[i] += offset_y;
}
}
setup_field(field_x, field_y, bounds) {
if (bounds === undefined) {
bounds = this.get_bounds(field_x, field_y);
}
const level = this.get_level_from_bounds(bounds), offset = this.pow2(level - 1), count = field_x.length;
this.move_field(field_x, field_y, offset, offset);
this.root = this.setup_field_recurse(0, count - 1, field_x, field_y, level);
}
partition(start, end, test_field, other_field, offset) {
let i = start, j = end, swap;
while (i <= j) {
while (i <= end && (test_field[i] & offset) === 0) {
i++;
}
while (j > start && test_field[j] & offset) {
j--;
}
if (i >= j) {
break;
}
swap = test_field[i];
test_field[i] = test_field[j];
test_field[j] = swap;
swap = other_field[i];
other_field[i] = other_field[j];
other_field[j] = swap;
i++;
j--;
}
return i;
}
setup_field_recurse(start, end, field_x, field_y, level) {
if (start > end) {
return this.empty_tree(level);
}
if (level === 2) {
return this.level2_setup(start, end, field_x, field_y);
}
level--;
const offset = 1 << level, part3 = this.partition(start, end, field_y, field_x, offset), part2 = this.partition(start, part3 - 1, field_x, field_y, offset), part4 = this.partition(part3, end, field_x, field_y, offset);
return this.create_tree(this.setup_field_recurse(start, part2 - 1, field_x, field_y, level), this.setup_field_recurse(part2, part3 - 1, field_x, field_y, level), this.setup_field_recurse(part3, part4 - 1, field_x, field_y, level), this.setup_field_recurse(part4, end, field_x, field_y, level));
}
level2_setup(start, end, field_x, field_y) {
let set = 0, x, y;
for (let i = start; i <= end; i++) {
x = field_x[i];
y = field_y[i];
set |= 1 << ((x & 1) | (((y & 1) | (x & 2)) << 1) | ((y & 2) << 2));
}
if (this.level2_cache[set]) {
return this.level2_cache[set];
}
return (this.level2_cache[set] = this.create_tree(this.level1_create(set), this.level1_create(set >> 4), this.level1_create(set >> 8), this.level1_create(set >> 12)));
}
set_step(step) {
if (step !== this.step) {
this.step = step;
this.uncache(false);
this.empty_tree_cache = [];
this.level2_cache = Array(0x10000);
}
}
set_rules(s, b) {
if (this.rule_s !== s || this.rule_b !== b) {
this.rule_s = s;
this.rule_b = b;
this.uncache(true);
this.empty_tree_cache = [];
this.level2_cache = Array(0x10000);
}
}
node_set_bit(node, x, y, living) {
if (node.level === 0) {
return living ? this.true_leaf : this.false_leaf;
}
const offset = node.level === 1 ? 0 : this.pow2(node.level - 2);
let nw = node.nw, ne = node.ne, sw = node.sw, se = node.se;
if (x < 0) {
if (y < 0) {
nw = this.node_set_bit(nw, x + offset, y + offset, living);
}
else {
sw = this.node_set_bit(sw, x + offset, y - offset, living);
}
}
else {
if (y < 0) {
ne = this.node_set_bit(ne, x - offset, y + offset, living);
}
else {
se = this.node_set_bit(se, x - offset, y - offset, living);
}
}
return this.create_tree(nw, ne, sw, se);
}
node_get_bit(node, x, y) {
if (node.population === 0) {
return false;
}
if (node.level === 0) {
return true;
}
const offset = node.level === 1 ? 0 : this.pow2(node.level - 2);
if (x < 0) {
if (y < 0) {
return this.node_get_bit(node.nw, x + offset, y + offset);
}
else {
return this.node_get_bit(node.sw, x + offset, y - offset);
}
}
else {
if (y < 0) {
return this.node_get_bit(node.ne, x - offset, y + offset);
}
else {
return this.node_get_bit(node.se, x - offset, y - offset);
}
}
}
node_get_field(node, left, top, field) {
if (node.population === 0) {
return;
}
if (node.level === 0) {
field.push({ x: left, y: top });
}
else {
const offset = this.pow2(node.level - 1);
this.node_get_field(node.nw, left, top, field);
this.node_get_field(node.sw, left, top + offset, field);
this.node_get_field(node.ne, left + offset, top, field);
this.node_get_field(node.se, left + offset, top + offset, field);
}
}
node_level2_next(node) {
const nw = node.nw, ne = node.ne, sw = node.sw, se = node.se;
const bitmask = (nw.nw.population << 15) |
(nw.ne.population << 14) |
(ne.nw.population << 13) |
(ne.ne.population << 12) |
(nw.sw.population << 11) |
(nw.se.population << 10) |
(ne.sw.population << 9) |
(ne.se.population << 8) |
(sw.nw.population << 7) |
(sw.ne.population << 6) |
(se.nw.population << 5) |
(se.ne.population << 4) |
(sw.sw.population << 3) |
(sw.se.population << 2) |
(se.sw.population << 1) |
se.se.population;
return this.level1_create(this.eval_mask(bitmask >> 5) |
(this.eval_mask(bitmask >> 4) << 1) |
(this.eval_mask(bitmask >> 1) << 2) |
(this.eval_mask(bitmask) << 3));
}
node_next_generation(node) {
if (node.cache) {
return node.cache;
}
if (this.step === node.level - 2) {
return this.node_quick_next_generation(node);
}
if (node.level === 2) {
if (node.quick_cache) {
return node.quick_cache;
}
else {
return (node.quick_cache = this.node_level2_next(node));
}
}
const nw = node.nw, ne = node.ne, sw = node.sw, se = node.se, n00 = this.create_tree(nw.nw.se, nw.ne.sw, nw.sw.ne, nw.se.nw), n01 = this.create_tree(nw.ne.se, ne.nw.sw, nw.se.ne, ne.sw.nw), n02 = this.create_tree(ne.nw.se, ne.ne.sw, ne.sw.ne, ne.se.nw), n10 = this.create_tree(nw.sw.se, nw.se.sw, sw.nw.ne, sw.ne.nw), n11 = this.create_tree(nw.se.se, ne.sw.sw, sw.ne.ne, se.nw.nw), n12 = this.create_tree(ne.sw.se, ne.se.sw, se.nw.ne, se.ne.nw), n20 = this.create_tree(sw.nw.se, sw.ne.sw, sw.sw.ne, sw.se.nw), n21 = this.create_tree(sw.ne.se, se.nw.sw, sw.se.ne, se.sw.nw), n22 = this.create_tree(se.nw.se, se.ne.sw, se.sw.ne, se.se.nw);
return (node.cache = this.create_tree(this.node_next_generation(this.create_tree(n00, n01, n10, n11)), this.node_next_generation(this.create_tree(n01, n02, n11, n12)), this.node_next_generation(this.create_tree(n10, n11, n20, n21)), this.node_next_generation(this.create_tree(n11, n12, n21, n22))));
}
node_quick_next_generation(node) {
if (node.quick_cache !== null) {
return node.quick_cache;
}
if (node.level === 2) {
return (node.quick_cache = this.node_level2_next(node));
}
const nw = node.nw, ne = node.ne, sw = node.sw, se = node.se, n00 = this.node_quick_next_generation(nw), n01 = this.node_quick_next_generation(this.create_tree(nw.ne, ne.nw, nw.se, ne.sw)), n02 = this.node_quick_next_generation(ne), n10 = this.node_quick_next_generation(this.create_tree(nw.sw, nw.se, sw.nw, sw.ne)), n11 = this.node_quick_next_generation(this.create_tree(nw.se, ne.sw, sw.ne, se.nw)), n12 = this.node_quick_next_generation(this.create_tree(ne.sw, ne.se, se.nw, se.ne)), n20 = this.node_quick_next_generation(sw), n21 = this.node_quick_next_generation(this.create_tree(sw.ne, se.nw, sw.se, se.sw)), n22 = this.node_quick_next_generation(se);
return (node.quick_cache = this.create_tree(this.node_quick_next_generation(this.create_tree(n00, n01, n10, n11)), this.node_quick_next_generation(this.create_tree(n01, n02, n11, n12)), this.node_quick_next_generation(this.create_tree(n10, n11, n20, n21)), this.node_quick_next_generation(this.create_tree(n11, n12, n21, n22))));
}
node_hash(node) {
if (!this.in_hashmap(node)) {
node.id = this.last_id++;
node.hashmap_next = undefined;
if (node.level > 1) {
this.node_hash(node.nw);
this.node_hash(node.ne);
this.node_hash(node.sw);
this.node_hash(node.se);
if (node.cache) {
this.node_hash(node.cache);
}
if (node.quick_cache) {
this.node_hash(node.quick_cache);
}
}
this.hashmap_insert(node);
}
}
node_get_boundary(node, left, top, find_mask, boundary) {
if (node.population === 0 || !find_mask) {
return;
}
if (node.level === 0) {
if (left < boundary.left)
boundary.left = left;
if (left > boundary.right)
boundary.right = left;
if (top < boundary.top)
boundary.top = top;
if (top > boundary.bottom)
boundary.bottom = top;
}
else {
const offset = this.pow2(node.level - 1);
if (left >= boundary.left &&
left + offset * 2 <= boundary.right &&
top >= boundary.top &&
top + offset * 2 <= boundary.bottom) {
return;
}
let find_nw = find_mask, find_sw = find_mask, find_ne = find_mask, find_se = find_mask;
if (node.nw.population) {
find_sw &= ~MASK_TOP;
find_ne &= ~MASK_LEFT;
find_se &= ~MASK_TOP & ~MASK_LEFT;
}
if (node.sw.population) {
find_se &= ~MASK_LEFT;
find_nw &= ~MASK_BOTTOM;
find_ne &= ~MASK_BOTTOM & ~MASK_LEFT;
}
if (node.ne.population) {
find_nw &= ~MASK_RIGHT;
find_se &= ~MASK_TOP;
find_sw &= ~MASK_TOP & ~MASK_RIGHT;
}
if (node.se.population) {
find_sw &= ~MASK_RIGHT;
find_ne &= ~MASK_BOTTOM;
find_nw &= ~MASK_BOTTOM & ~MASK_RIGHT;
}
this.node_get_boundary(node.nw, left, top, find_nw, boundary);
this.node_get_boundary(node.sw, left, top + offset, find_sw, boundary);
this.node_get_boundary(node.ne, left + offset, top, find_ne, boundary);
this.node_get_boundary(node.se, left + offset, top + offset, find_se, boundary);
}
}
}
exports.LifeUniverse = LifeUniverse;
//# sourceMappingURL=life-universe.js.map