UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

284 lines (215 loc) • 5.6 kB
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;