@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
284 lines (215 loc) • 5.6 kB
JavaScript
import { assert } from "../../assert.js";
/**
* Min-Heap implementation with a score function.
* The data structure is a binary heap where elements are removed in order defined by scoring function
* @template T
*/
class BinaryHeap {
/**
* @private
* @type {T[]}
*/
data = [];
/**
* @private
* @type {number}
*/
length = 0;
/**
* @template T
* @param {function(el:T):number} scoreFunction
* @constructor
*/
constructor(scoreFunction) {
/**
*
* @type {function(T): number}
*/
this.scoreFunction = scoreFunction;
}
/**
* @private
* @param {number} pos
*/
bubbleUp(pos) {
assert.isNumber(pos, "pos");
const data = this.data;
let parentIndex;
let index = pos;
let value = data[index];
let score = this.scoreFunction(value);
while (index > 0) {
parentIndex = (index - 1) >> 1;
const parentValue = data[parentIndex];
const parentScore = this.scoreFunction(parentValue);
if (score < parentScore) {
//swap
data[parentIndex] = value;
data[index] = parentValue;
index = parentIndex;
} else {
break;
}
}
}
/**
* @private
* @param {number} pos
*/
bubbleDown(pos) {
assert.isNonNegativeInteger(pos, 'pos');
const data = this.data;
const length = this.length;
let index = pos;
let minIndex = index;
for (; ;) {
const left = (index << 1) + 1;
if (left >= length) {
//index is a leaf node
break;
}
const right = left + 1;
let dataMin = data[minIndex];
const scoreMin = this.scoreFunction(dataMin);
const dataLeft = data[left];
const scoreLeft = this.scoreFunction(dataLeft);
if (right >= length) {
//right node doesn't exist
if (scoreLeft >= scoreMin) {
break;
} else {
minIndex = left;
dataMin = dataLeft;
}
} else {
//both left and right nodes exist
const dataRight = data[right];
const scoreRight = this.scoreFunction(dataRight);
if (scoreLeft <= scoreRight) {
if (scoreLeft >= scoreMin) {
break;
} else {
minIndex = left;
dataMin = dataLeft;
}
} else {
if (scoreRight >= scoreMin) {
break;
} else {
minIndex = right;
dataMin = dataRight;
}
}
}
//swap positions
data[minIndex] = data[index];
data[index] = dataMin;
index = minIndex;
}
}
/**
*
* @return {T}
*/
pop() {
this.length--;
const new_length = this.length;
const last = this.data.pop();
if (new_length === 0) {
// this was the last element in the heap
return last;
}
const ret = this.data[0];
this.data[0] = last;
this.bubbleDown(0);
return ret;
}
/**
*
* @returns {T|undefined}
*/
peek() {
return this.data[0];
}
/**
*
* @param {T} node
* @returns {boolean}
*/
delete(node) {
const i = this.data.indexOf(node);
if (i === -1) {
return false;
}
this.deleteByIndex(i);
return true;
}
/**
* NOTE: Unsafe method, use at your own risk
* @param {number} i
*/
deleteByIndex(i) {
this.data.splice(i, 1);
this.length--;
}
/**
* Remove all the data from the heap
*/
clear() {
this.data = [];
this.length = 0;
}
/**
*
* @param {T} node
* @returns {boolean}
*/
contains(node) {
const index = this.data.indexOf(node);
return index !== -1;
}
/**
*
* @returns {boolean}
*/
isEmpty() {
return this.length === 0;
}
/**
*
* @returns {number}
*/
size() {
return this.length;
}
/**
*
* @param {T} node
* @returns {boolean} false if element is not found, true otherwise
*/
updateElementScore(node) {
const index = this.data.indexOf(node);
if (index === -1) {
return false;
}
this.bubbleDown(index);
return true;
}
/**
*
* @param {T} el
*/
push(el) {
this.data.push(el);
const position = this.length;
this.length = position + 1;
this.bubbleUp(position);
}
}
/**
* Useful for type checks
* @readonly
* @type {boolean}
*/
BinaryHeap.prototype.isBinaryHeap = true;
export default BinaryHeap;