From 4a1015fb5b6416e681d3a4ecee24e2d06526a302 Mon Sep 17 00:00:00 2001 From: Marcin Czerniak Date: Sun, 20 Jun 2021 19:53:17 +0200 Subject: [PATCH] chore: Priority quest pathfinding, orders window --- src/index.html | 25 +- src/logic/agent.js | 61 ++- src/logic/heap.js | 869 ++++++++++++++++++++++++++++++++++++ src/logic/pathfinding.js | 68 +-- src/logic/product.js | 3 - src/logic/shelf.js | 12 +- src/logic/time.js | 4 +- src/main.js | 3 +- src/styles.css | 108 +++++ src/view/agentController.js | 34 +- src/view/grid.js | 70 ++- src/view/ordersView.js | 101 +++++ 12 files changed, 1285 insertions(+), 73 deletions(-) create mode 100644 src/logic/heap.js create mode 100644 src/view/ordersView.js diff --git a/src/index.html b/src/index.html index 8def0ba..9571bf5 100644 --- a/src/index.html +++ b/src/index.html @@ -17,6 +17,7 @@ + @@ -26,6 +27,7 @@ + @@ -49,7 +51,9 @@
-
+
+ +
logo
@@ -301,5 +305,24 @@
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+ +
+ + +
+
+
diff --git a/src/logic/agent.js b/src/logic/agent.js index 9c4ed69..5ca2fa5 100644 --- a/src/logic/agent.js +++ b/src/logic/agent.js @@ -8,16 +8,21 @@ class Agent extends AgentController { Agent.instance = this; const cycle = async () => { - const jobRequest = Products.instance.getJobRequest(); - if (jobRequest) { - await this.takeFromStore(jobRequest.product, jobRequest.amount); - await this.deliver(jobRequest.x, jobRequest.y); - } else { - await waitFor(1000); + try { + const jobRequest = Products.instance.getJobRequest(); + if (jobRequest) { + await this.takeFromStore(jobRequest.product, jobRequest.amount); + await this.deliver(jobRequest.x, jobRequest.y); + } else { + await waitFor(1000); + } + + await waitFor(this.waitTime); + + cycle(); + } catch (e) { + console.log(e); } - - await waitFor(this.waitTime); - cycle(); }; setTimeout(() => cycle(), 2000); @@ -38,12 +43,16 @@ class Agent extends AgentController { * @param {number} y Współrzędna y */ async deliver(x, y) { - await this.moveTo(x, y); - await waitFor(this.waitTime); - Products.instance.exchange(x, y, this.ownedProduct, 50); + try { + await this.moveTo(x, y); + await this.waitFor(this.waitTime); + Products.instance.exchange(x, y, this.ownedProduct, 50); - this.ownedProductAmount = 0; - this.ownedProduct = null; + this.ownedProductAmount = 0; + this.ownedProduct = null; + } catch (e) { + throw e; + } } /** @@ -51,10 +60,26 @@ class Agent extends AgentController { * @param {*} product */ async takeFromStore(product, amount) { - await this.moveTo(9, 8); - await waitFor(this.waitTime); - this.ownedProductAmount = amount; - this.ownedProduct = product; + try { + await this.moveTo(8, 8); + await this.waitFor(this.waitTime); + this.ownedProductAmount = amount; + this.ownedProduct = product; + } catch (e) { + throw e; + } + } + + async deliverToOrders() { + try { + await this.moveTo(x, y); + await this.waitFor(this.waitTime); + + this.ownedProductAmount = 0; + this.ownedProduct = null; + } catch (e) { + throw e; + } } /** diff --git a/src/logic/heap.js b/src/logic/heap.js new file mode 100644 index 0000000..0b50bda --- /dev/null +++ b/src/logic/heap.js @@ -0,0 +1,869 @@ +/** + * Heap + * @type {Class} + */ +var Heap = /** @class */ (function () { + /** + * Heap instance constructor. + * @param {Function} compare Optional comparison function, defaults to Heap.minComparator + */ + function Heap(compare) { + var _this = this; + if (compare === void 0) { compare = Heap.minComparator; } + this.compare = compare; + this.heapArray = []; + this._limit = 0; + /** + * Alias of add + */ + this.offer = this.add; + /** + * Alias of peek + */ + this.element = this.peek; + /** + * Alias of pop + */ + this.poll = this.pop; + /** + * Returns the inverse to the comparison function. + * @return {Function} + */ + this._invertedCompare = function (a, b) { + return -1 * _this.compare(a, b); + }; + } + /* + Static methods + */ + /** + * Gets children indices for given index. + * @param {Number} idx Parent index + * @return {Array(Number)} Array of children indices + */ + Heap.getChildrenIndexOf = function (idx) { + return [idx * 2 + 1, idx * 2 + 2]; + }; + /** + * Gets parent index for given index. + * @param {Number} idx Children index + * @return {Number | undefined} Parent index, -1 if idx is 0 + */ + Heap.getParentIndexOf = function (idx) { + if (idx <= 0) { + return -1; + } + var whichChildren = idx % 2 ? 1 : 2; + return Math.floor((idx - whichChildren) / 2); + }; + /** + * Gets sibling index for given index. + * @param {Number} idx Children index + * @return {Number | undefined} Sibling index, -1 if idx is 0 + */ + Heap.getSiblingIndexOf = function (idx) { + if (idx <= 0) { + return -1; + } + var whichChildren = idx % 2 ? 1 : -1; + return idx + whichChildren; + }; + /** + * Min heap comparison function, default. + * @param {any} a First element + * @param {any} b Second element + * @return {Number} 0 if they're equal, positive if `a` goes up, negative if `b` goes up + */ + Heap.minComparator = function (a, b) { + if (a > b) { + return 1; + } + else if (a < b) { + return -1; + } + else { + return 0; + } + }; + /** + * Max heap comparison function. + * @param {any} a First element + * @param {any} b Second element + * @return {Number} 0 if they're equal, positive if `a` goes up, negative if `b` goes up + */ + Heap.maxComparator = function (a, b) { + if (b > a) { + return 1; + } + else if (b < a) { + return -1; + } + else { + return 0; + } + }; + /** + * Min number heap comparison function, default. + * @param {Number} a First element + * @param {Number} b Second element + * @return {Number} 0 if they're equal, positive if `a` goes up, negative if `b` goes up + */ + Heap.minComparatorNumber = function (a, b) { + return a - b; + }; + /** + * Max number heap comparison function. + * @param {Number} a First element + * @param {Number} b Second element + * @return {Number} 0 if they're equal, positive if `a` goes up, negative if `b` goes up + */ + Heap.maxComparatorNumber = function (a, b) { + return b - a; + }; + /** + * Default equality function. + * @param {any} a First element + * @param {any} b Second element + * @return {Boolean} True if equal, false otherwise + */ + Heap.defaultIsEqual = function (a, b) { + return a === b; + }; + /** + * Prints a heap. + * @param {Heap} heap Heap to be printed + * @returns {String} + */ + Heap.print = function (heap) { + function deep(i) { + var pi = Heap.getParentIndexOf(i); + return Math.floor(Math.log2(pi + 1)); + } + function repeat(str, times) { + var out = ''; + for (; times > 0; --times) { + out += str; + } + return out; + } + var node = 0; + var lines = []; + var maxLines = deep(heap.length - 1) + 2; + var maxLength = 0; + while (node < heap.length) { + var i = deep(node) + 1; + if (node === 0) { + i = 0; + } + // Text representation + var nodeText = String(heap.get(node)); + if (nodeText.length > maxLength) { + maxLength = nodeText.length; + } + // Add to line + lines[i] = lines[i] || []; + lines[i].push(nodeText); + node += 1; + } + return lines + .map(function (line, i) { + var times = Math.pow(2, maxLines - i) - 1; + return (repeat(' ', Math.floor(times / 2) * maxLength) + + line + .map(function (el) { + // centered + var half = (maxLength - el.length) / 2; + return repeat(' ', Math.ceil(half)) + el + repeat(' ', Math.floor(half)); + }) + .join(repeat(' ', times * maxLength))); + }) + .join('\n'); + }; + /* + Python style + */ + /** + * Converts an array into an array-heap, in place + * @param {Array} arr Array to be modified + * @param {Function} compare Optional compare function + * @return {Heap} For convenience, it returns a Heap instance + */ + Heap.heapify = function (arr, compare) { + var heap = new Heap(compare); + heap.heapArray = arr; + heap.init(); + return heap; + }; + /** + * Extract the peek of an array-heap + * @param {Array} heapArr Array to be modified, should be a heap + * @param {Function} compare Optional compare function + * @return {any} Returns the extracted peek + */ + Heap.heappop = function (heapArr, compare) { + var heap = new Heap(compare); + heap.heapArray = heapArr; + return heap.pop(); + }; + /** + * Pushes a item into an array-heap + * @param {Array} heapArr Array to be modified, should be a heap + * @param {any} item Item to push + * @param {Function} compare Optional compare function + */ + Heap.heappush = function (heapArr, item, compare) { + var heap = new Heap(compare); + heap.heapArray = heapArr; + heap.push(item); + }; + /** + * Push followed by pop, faster + * @param {Array} heapArr Array to be modified, should be a heap + * @param {any} item Item to push + * @param {Function} compare Optional compare function + * @return {any} Returns the extracted peek + */ + Heap.heappushpop = function (heapArr, item, compare) { + var heap = new Heap(compare); + heap.heapArray = heapArr; + return heap.pushpop(item); + }; + /** + * Replace peek with item + * @param {Array} heapArr Array to be modified, should be a heap + * @param {any} item Item as replacement + * @param {Function} compare Optional compare function + * @return {any} Returns the extracted peek + */ + Heap.heapreplace = function (heapArr, item, compare) { + var heap = new Heap(compare); + heap.heapArray = heapArr; + return heap.replace(item); + }; + /** + * Return the `n` most valuable elements of a heap-like Array + * @param {Array} heapArr Array, should be an array-heap + * @param {number} n Max number of elements + * @param {Function} compare Optional compare function + * @return {any} Elements + */ + Heap.heaptop = function (heapArr, n, compare) { + if (n === void 0) { n = 1; } + var heap = new Heap(compare); + heap.heapArray = heapArr; + return heap.top(n); + }; + /** + * Return the `n` least valuable elements of a heap-like Array + * @param {Array} heapArr Array, should be an array-heap + * @param {number} n Max number of elements + * @param {Function} compare Optional compare function + * @return {any} Elements + */ + Heap.heapbottom = function (heapArr, n, compare) { + if (n === void 0) { n = 1; } + var heap = new Heap(compare); + heap.heapArray = heapArr; + return heap.bottom(n); + }; + /** + * Return the `n` most valuable elements of an iterable + * @param {number} n Max number of elements + * @param {Iterable} Iterable Iterable list of elements + * @param {Function} compare Optional compare function + * @return {any} Elements + */ + Heap.nlargest = function (n, iterable, compare) { + var heap = new Heap(compare); + heap.heapArray = __spreadArrays(iterable); + heap.init(); + return heap.top(n); + }; + /** + * Return the `n` least valuable elements of an iterable + * @param {number} n Max number of elements + * @param {Iterable} Iterable Iterable list of elements + * @param {Function} compare Optional compare function + * @return {any} Elements + */ + Heap.nsmallest = function (n, iterable, compare) { + var heap = new Heap(compare); + heap.heapArray = __spreadArrays(iterable); + heap.init(); + return heap.bottom(n); + }; + /* + Instance methods + */ + /** + * Adds an element to the heap. Aliases: `offer`. + * Same as: push(element) + * @param {any} element Element to be added + * @return {Boolean} true + */ + Heap.prototype.add = function (element) { + this._sortNodeUp(this.heapArray.push(element) - 1); + this._applyLimit(); + return true; + }; + /** + * Adds an array of elements to the heap. + * Similar as: push(element, element, ...). + * @param {Array} elements Elements to be added + * @return {Boolean} true + */ + Heap.prototype.addAll = function (elements) { + var _a; + var i = this.length; + (_a = this.heapArray).push.apply(_a, elements); + for (var l = this.length; i < l; ++i) { + this._sortNodeUp(i); + } + this._applyLimit(); + return true; + }; + /** + * Return the bottom (lowest value) N elements of the heap. + * + * @param {Number} n Number of elements. + * @return {Array} Array of length <= N. + */ + Heap.prototype.bottom = function (n) { + if (n === void 0) { n = 1; } + if (this.heapArray.length === 0 || n <= 0) { + // Nothing to do + return []; + } + else if (this.heapArray.length === 1) { + // Just the peek + return [this.heapArray[0]]; + } + else if (n >= this.heapArray.length) { + // The whole heap + return __spreadArrays(this.heapArray); + } + else { + // Some elements + var result = this._bottomN_push(~~n); + return result; + } + }; + /** + * Check if the heap is sorted, useful for testing purposes. + * @return {Undefined | Element} Returns an element if something wrong is found, otherwise it's undefined + */ + Heap.prototype.check = function () { + var _this = this; + return this.heapArray.find(function (el, j) { return !!_this.getChildrenOf(j).find(function (ch) { return _this.compare(el, ch) > 0; }); }); + }; + /** + * Remove all of the elements from this heap. + */ + Heap.prototype.clear = function () { + this.heapArray = []; + }; + /** + * Clone this heap + * @return {Heap} + */ + Heap.prototype.clone = function () { + var cloned = new Heap(this.comparator()); + cloned.heapArray = this.toArray(); + cloned._limit = this._limit; + return cloned; + }; + /** + * Returns the comparison function. + * @return {Function} + */ + Heap.prototype.comparator = function () { + return this.compare; + }; + /** + * Returns true if this queue contains the specified element. + * @param {any} o Element to be found + * @param {Function} fn Optional comparison function, receives (element, needle) + * @return {Boolean} + */ + Heap.prototype.contains = function (o, fn) { + if (fn === void 0) { fn = Heap.defaultIsEqual; } + return this.heapArray.findIndex(function (el) { return fn(el, o); }) >= 0; + }; + /** + * Initialise a heap, sorting nodes + * @param {Array} array Optional initial state array + */ + Heap.prototype.init = function (array) { + if (array) { + this.heapArray = __spreadArrays(array); + } + for (var i = Math.floor(this.heapArray.length); i >= 0; --i) { + this._sortNodeDown(i); + } + this._applyLimit(); + }; + /** + * Test if the heap has no elements. + * @return {Boolean} True if no elements on the heap + */ + Heap.prototype.isEmpty = function () { + return this.length === 0; + }; + /** + * Get the leafs of the tree (no children nodes) + */ + Heap.prototype.leafs = function () { + if (this.heapArray.length === 0) { + return []; + } + var pi = Heap.getParentIndexOf(this.heapArray.length - 1); + return this.heapArray.slice(pi + 1); + }; + Object.defineProperty(Heap.prototype, "length", { + /** + * Length of the heap. + * @return {Number} + */ + get: function () { + return this.heapArray.length; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(Heap.prototype, "limit", { + /** + * Get length limit of the heap. + * @return {Number} + */ + get: function () { + return this._limit; + }, + /** + * Set length limit of the heap. + * @return {Number} + */ + set: function (_l) { + this._limit = ~~_l; + this._applyLimit(); + }, + enumerable: false, + configurable: true + }); + /** + * Top node. Aliases: `element`. + * Same as: `top(1)[0]` + * @return {any} Top node + */ + Heap.prototype.peek = function () { + return this.heapArray[0]; + }; + /** + * Extract the top node (root). Aliases: `poll`. + * @return {any} Extracted top node, undefined if empty + */ + Heap.prototype.pop = function () { + var last = this.heapArray.pop(); + if (this.length > 0 && last !== undefined) { + return this.replace(last); + } + return last; + }; + /** + * Pushes element(s) to the heap. + * @param {...any} elements Elements to insert + * @return {Boolean} True if elements are present + */ + Heap.prototype.push = function () { + var elements = []; + for (var _i = 0; _i < arguments.length; _i++) { + elements[_i] = arguments[_i]; + } + if (elements.length < 1) { + return false; + } + else if (elements.length === 1) { + return this.add(elements[0]); + } + else { + return this.addAll(elements); + } + }; + /** + * Same as push & pop in sequence, but faster + * @param {any} element Element to insert + * @return {any} Extracted top node + */ + Heap.prototype.pushpop = function (element) { + var _a; + if (this.compare(this.heapArray[0], element) < 0) { + _a = [this.heapArray[0], element], element = _a[0], this.heapArray[0] = _a[1]; + this._sortNodeDown(0); + } + return element; + }; + /** + * Remove an element from the heap. + * @param {any} o Element to be found + * @param {Function} fn Optional function to compare + * @return {Boolean} True if the heap was modified + */ + Heap.prototype.remove = function (o, fn) { + if (fn === void 0) { fn = Heap.defaultIsEqual; } + if (this.length > 0) { + if (o === undefined) { + this.pop(); + return true; + } + else { + var idx = this.heapArray.findIndex(function (el) { return fn(el, o); }); + if (idx >= 0) { + if (idx === 0) { + this.pop(); + } + else if (idx === this.length - 1) { + this.heapArray.pop(); + } + else { + this.heapArray.splice(idx, 1, this.heapArray.pop()); + this._sortNodeUp(idx); + this._sortNodeDown(idx); + } + return true; + } + } + } + return false; + }; + /** + * Pop the current peek value, and add the new item. + * @param {any} element Element to replace peek + * @return {any} Old peek + */ + Heap.prototype.replace = function (element) { + var peek = this.heapArray[0]; + this.heapArray[0] = element; + this._sortNodeDown(0); + return peek; + }; + /** + * Size of the heap + * @return {Number} + */ + Heap.prototype.size = function () { + return this.length; + }; + /** + * Return the top (highest value) N elements of the heap. + * + * @param {Number} n Number of elements. + * @return {Array} Array of length <= N. + */ + Heap.prototype.top = function (n) { + if (n === void 0) { n = 1; } + if (this.heapArray.length === 0 || n <= 0) { + // Nothing to do + return []; + } + else if (this.heapArray.length === 1 || n === 1) { + // Just the peek + return [this.heapArray[0]]; + } + else if (n >= this.heapArray.length) { + // The whole peek + return __spreadArrays(this.heapArray); + } + else { + // Some elements + var result = this._topN_push(~~n); + return result; + } + }; + /** + * Clone the heap's internal array + * @return {Array} + */ + Heap.prototype.toArray = function () { + return __spreadArrays(this.heapArray); + }; + /** + * String output, call to Array.prototype.toString() + * @return {String} + */ + Heap.prototype.toString = function () { + return this.heapArray.toString(); + }; + /** + * Get the element at the given index. + * @param {Number} i Index to get + * @return {any} Element at that index + */ + Heap.prototype.get = function (i) { + return this.heapArray[i]; + }; + /** + * Get the elements of these node's children + * @param {Number} idx Node index + * @return {Array(any)} Children elements + */ + Heap.prototype.getChildrenOf = function (idx) { + var _this = this; + return Heap.getChildrenIndexOf(idx) + .map(function (i) { return _this.heapArray[i]; }) + .filter(function (e) { return e !== undefined; }); + }; + /** + * Get the element of this node's parent + * @param {Number} idx Node index + * @return {any} Parent element + */ + Heap.prototype.getParentOf = function (idx) { + var pi = Heap.getParentIndexOf(idx); + return this.heapArray[pi]; + }; + /** + * Iterator interface + */ + Heap.prototype[Symbol.iterator] = function () { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + if (!this.length) return [3 /*break*/, 2]; + return [4 /*yield*/, this.pop()]; + case 1: + _a.sent(); + return [3 /*break*/, 0]; + case 2: return [2 /*return*/]; + } + }); + }; + /** + * Returns an iterator. To comply with Java interface. + */ + Heap.prototype.iterator = function () { + return this; + }; + /** + * Limit heap size if needed + */ + Heap.prototype._applyLimit = function () { + if (this._limit && this._limit < this.heapArray.length) { + var rm = this.heapArray.length - this._limit; + // It's much faster than splice + while (rm) { + this.heapArray.pop(); + --rm; + } + } + }; + /** + * Return the bottom (lowest value) N elements of the heap, without corner cases, unsorted + * + * @param {Number} n Number of elements. + * @return {Array} Array of length <= N. + */ + Heap.prototype._bottomN_push = function (n) { + // Use an inverted heap + var bottomHeap = new Heap(this.compare); + bottomHeap.limit = n; + bottomHeap.heapArray = this.heapArray.slice(-n); + bottomHeap.init(); + var startAt = this.heapArray.length - 1 - n; + var parentStartAt = Heap.getParentIndexOf(startAt); + var indices = []; + for (var i = startAt; i > parentStartAt; --i) { + indices.push(i); + } + var arr = this.heapArray; + while (indices.length) { + var i = indices.shift(); + if (this.compare(arr[i], bottomHeap.peek()) > 0) { + bottomHeap.replace(arr[i]); + if (i % 2) { + indices.push(Heap.getParentIndexOf(i)); + } + } + } + return bottomHeap.toArray(); + }; + /** + * Move a node to a new index, switching places + * @param {Number} j First node index + * @param {Number} k Another node index + */ + Heap.prototype._moveNode = function (j, k) { + var _a; + _a = [this.heapArray[k], this.heapArray[j]], this.heapArray[j] = _a[0], this.heapArray[k] = _a[1]; + }; + /** + * Move a node down the tree (to the leaves) to find a place where the heap is sorted. + * @param {Number} i Index of the node + */ + Heap.prototype._sortNodeDown = function (i) { + var _this = this; + var moveIt = i < this.heapArray.length - 1; + var self = this.heapArray[i]; + var getPotentialParent = function (best, j) { + if (_this.heapArray.length > j && _this.compare(_this.heapArray[j], _this.heapArray[best]) < 0) { + best = j; + } + return best; + }; + while (moveIt) { + var childrenIdx = Heap.getChildrenIndexOf(i); + var bestChildIndex = childrenIdx.reduce(getPotentialParent, childrenIdx[0]); + var bestChild = this.heapArray[bestChildIndex]; + if (typeof bestChild !== 'undefined' && this.compare(self, bestChild) > 0) { + this._moveNode(i, bestChildIndex); + i = bestChildIndex; + } + else { + moveIt = false; + } + } + }; + /** + * Move a node up the tree (to the root) to find a place where the heap is sorted. + * @param {Number} i Index of the node + */ + Heap.prototype._sortNodeUp = function (i) { + var moveIt = i > 0; + while (moveIt) { + var pi = Heap.getParentIndexOf(i); + if (pi >= 0 && this.compare(this.heapArray[pi], this.heapArray[i]) > 0) { + this._moveNode(i, pi); + i = pi; + } + else { + moveIt = false; + } + } + }; + /** + * Return the top (highest value) N elements of the heap, without corner cases, unsorted + * Implementation: push. + * + * @param {Number} n Number of elements. + * @return {Array} Array of length <= N. + */ + Heap.prototype._topN_push = function (n) { + // Use an inverted heap + var topHeap = new Heap(this._invertedCompare); + topHeap.limit = n; + var indices = [0]; + var arr = this.heapArray; + while (indices.length) { + var i = indices.shift(); + if (i < arr.length) { + if (topHeap.length < n) { + topHeap.push(arr[i]); + indices.push.apply(indices, Heap.getChildrenIndexOf(i)); + } + else if (this.compare(arr[i], topHeap.peek()) < 0) { + topHeap.replace(arr[i]); + indices.push.apply(indices, Heap.getChildrenIndexOf(i)); + } + } + } + return topHeap.toArray(); + }; + /** + * Return the top (highest value) N elements of the heap, without corner cases, unsorted + * Implementation: init + push. + * + * @param {Number} n Number of elements. + * @return {Array} Array of length <= N. + */ + Heap.prototype._topN_fill = function (n) { + // Use an inverted heap + var heapArray = this.heapArray; + var topHeap = new Heap(this._invertedCompare); + topHeap.limit = n; + topHeap.heapArray = heapArray.slice(0, n); + topHeap.init(); + var branch = Heap.getParentIndexOf(n - 1) + 1; + var indices = []; + for (var i = branch; i < n; ++i) { + indices.push.apply(indices, Heap.getChildrenIndexOf(i).filter(function (l) { return l < heapArray.length; })); + } + if ((n - 1) % 2) { + indices.push(n); + } + while (indices.length) { + var i = indices.shift(); + if (i < heapArray.length) { + if (this.compare(heapArray[i], topHeap.peek()) < 0) { + topHeap.replace(heapArray[i]); + indices.push.apply(indices, Heap.getChildrenIndexOf(i)); + } + } + } + return topHeap.toArray(); + }; + /** + * Return the top (highest value) N elements of the heap, without corner cases, unsorted + * Implementation: heap. + * + * @param {Number} n Number of elements. + * @return {Array} Array of length <= N. + */ + Heap.prototype._topN_heap = function (n) { + var topHeap = this.clone(); + var result = []; + for (var i = 0; i < n; ++i) { + result.push(topHeap.pop()); + } + return result; + }; + /** + * Return index of the top element + * @param list + */ + Heap.prototype._topIdxOf = function (list) { + if (!list.length) { + return -1; + } + var idx = 0; + var top = list[idx]; + for (var i = 1; i < list.length; ++i) { + var comp = this.compare(list[i], top); + if (comp < 0) { + idx = i; + top = list[i]; + } + } + return idx; + }; + /** + * Return the top element + * @param list + */ + Heap.prototype._topOf = function () { + var list = []; + for (var _i = 0; _i < arguments.length; _i++) { + list[_i] = arguments[_i]; + } + var heap = new Heap(this.compare); + heap.init(list); + return heap.peek(); + }; + /** + * Swap identical elements compared by comparator function + */ + Heap.prototype.swap = function(el, cmp) { + if (!cmp) { + cmp = Heap.defaultIsEqual; + } + this.remove(el, cmp); + this.push(el); + }; + /** + * Find element in heap + */ + Heap.prototype.getElement = function(o, cmp) { + if (!cmp) { + cmp = Heap.defaultIsEqual; + } + return this.heapArray.find(function (el) { return cmp(el, o); }); + }; + return Heap; +}()); diff --git a/src/logic/pathfinding.js b/src/logic/pathfinding.js index 817df29..6929827 100644 --- a/src/logic/pathfinding.js +++ b/src/logic/pathfinding.js @@ -10,13 +10,16 @@ const PATHFINDING_ACTION = Enum('ROTATE', 'MOVE') - class Pathfinding { static search(state, expectedState) { return Pathfinding.graphSearch({ state }, { state: expectedState }) } - static graphSearch(node, expectedNode, fringe = [], explored = []) { + static priorityComparator(a, b) { + return a.totalCost - b.totalCost; + } + + static graphSearch(node, expectedNode, fringe = new Heap(Pathfinding.priorityComparator), explored = []) { fringe.push(node); while(true) { @@ -24,44 +27,47 @@ class Pathfinding { return false; } - const elem = fringe.shift(); + const elem = fringe.pop(); - if (Pathfinding.reached(elem.state, expectedNode.state)) { + if (Pathfinding.isEqual(elem.state, expectedNode.state)) { return Pathfinding.buildPath(node, elem); } explored.push(elem); for (const successor of this.successor(elem)) { - if (!Pathfinding.isNodeInArr(successor, explored) && !Pathfinding.isNodeInArr(successor, fringe)) { - Pathfinding.insertWithPriority(successor, fringe); - } else if (Pathfinding.isNodeInArr(successor, fringe)) { - const index = Pathfinding.findIndexOf(successor, fringe); - if (successor.totalCost < fringe[index].totalCost) { - fringe.splice(index, 1, successor); - } + const isExplored = Pathfinding.isNodeInArr(successor, explored); + const elementFromFringe = fringe.getElement(successor, Pathfinding.isNodesEqual); + const isInFringe = !(elementFromFringe === undefined); + + if (!isExplored && !isInFringe) { + fringe.push(successor); + } else if (isInFringe && elementFromFringe.totalCost > successor.totalCost) { + fringe.swap(successor, Pathfinding.isNodesEqual); } } + + // debugger; } } static isNodeInArr(node, arr) { - return !arr.every(n => !Pathfinding.reached(n.state, node.state)) + return !arr.every(n => !Pathfinding.isEqual(n.state, node.state)) } - static findIndexOf(node, arr) { - return arr.findIndex(n => Pathfinding.reached(n.state, node.state)); - } + // static findIndexOf(node, arr) { + // return arr.findIndex(n => Pathfinding.isEqual(n.state, node.state)); + // } - static insertWithPriority(node, arr) { - for (const [i, n] of arr.entries()) { - if (node.totalCost < n.totalCost) { - arr.splice(i, 0, node); - return; - } - } - arr.push(node); - } + // static insertWithPriority(node, arr) { + // for (const [i, n] of arr.entries()) { + // if (node.totalCost < n.totalCost) { + // arr.splice(i, 0, node); + // return; + // } + // } + // arr.push(node); + // } static successor(node) { const list = []; @@ -105,7 +111,7 @@ class Pathfinding { const result = []; let currentElement = expectedNode; - while(!Pathfinding.reached(currentElement.state, node.state)) { + while(!Pathfinding.isEqual(currentElement.state, node.state)) { result.unshift(currentElement); currentElement = currentElement.parent; } @@ -113,12 +119,16 @@ class Pathfinding { return result; } - static getManhattanDistance(node, expectedNode) { - return Math.abs(node.state.position.x - expectedNode.state.position.x) + Math.abs(node.state.position.y - expectedNode.state.position.y); + // static getManhattanDistance(node, expectedNode) { + // return Math.abs(node.state.position.x - expectedNode.state.position.x) + Math.abs(node.state.position.y - expectedNode.state.position.y); + // } + + static isEqual(state, expectedState) { + return state.rotation == expectedState.rotation && state.position.x == expectedState.position.x && state.position.y == expectedState.position.y; } - static reached(state, expectedState) { - return state.rotation == expectedState.rotation && state.position.x == expectedState.position.x && state.position.y == expectedState.position.y; + static isNodesEqual(node, expectedNode) { + return Pathfinding.isEqual(node.state, expectedNode.state); } static getVectorFromRotation(rotation) { diff --git a/src/logic/product.js b/src/logic/product.js index a3f56f3..e8055a6 100644 --- a/src/logic/product.js +++ b/src/logic/product.js @@ -23,7 +23,6 @@ class Product { equals(other) { return this.name === other.name; } -<<<<<<< HEAD static get REGISTRY() { // in alphabetical order @@ -154,6 +153,4 @@ class Product { static RANDOM_FROM_REGISTRY() { return Product.REGISTRY[Object.keys(Product.REGISTRY).filter(p => p !== 'empty')[Math.floor(Math.random() * (Object.keys(Product.REGISTRY).length - 1))]]; } -======= ->>>>>>> 4bd5bbdc0a26639410a6634debaff60c8a484593 } \ No newline at end of file diff --git a/src/logic/shelf.js b/src/logic/shelf.js index e7ffbdf..4d0991f 100644 --- a/src/logic/shelf.js +++ b/src/logic/shelf.js @@ -1,10 +1,10 @@ const SHELF_TYPE = Object.freeze({ - FRIDGE_SHELF: 'FRIDGE_SHELF', - LARGE_SHELF: 'LARGE_SHELF', - BASKET_SHELF: 'BASKET_SHELF', - DISCOUNT_SHELF: 'DISCOUNT_SHELF', - STANDARD_SHELF: 'STANDARD_SHELF', - FREESTANDING_SHELF: 'FREESTANDING_SHELF', + FRIDGE_SHELF: 'lodowka', + LARGE_SHELF: 'gabarytowa', + BASKET_SHELF: 'kosze', + DISCOUNT_SHELF: 'przecena', + STANDARD_SHELF: 'standardowa', + FREESTANDING_SHELF: 'wolnostojaca', }); class Shelf { diff --git a/src/logic/time.js b/src/logic/time.js index 263baf2..dd497d2 100644 --- a/src/logic/time.js +++ b/src/logic/time.js @@ -60,11 +60,13 @@ class Time { async addDays(days) { await Products.instance.next(days); Products.instance.update(); - const ul = document.querySelector('ul#history'); this.setDay(this.day += Number(days)); + const ul = document.querySelector('ul#history'); const li = document.createElement('li'); li.innerHTML = `${this.day}: ${nice(Products.instance.max_similarity)}`; ul.appendChild(li); + + Agent.instance.reset(); } } \ No newline at end of file diff --git a/src/main.js b/src/main.js index fa24462..922f442 100644 --- a/src/main.js +++ b/src/main.js @@ -12,7 +12,8 @@ window.addEventListener('DOMContentLoaded', async () => { const timeController = new TimeController(); const floor = new Floor(floorCanvas); const grid = new Grid(gridCanvas); - const agent = new Agent(agentCanvas); + new Agent(agentCanvas); + new OrdersView(); await Knowledge.load() const products = new Products(productsCanvas); diff --git a/src/styles.css b/src/styles.css index d529899..ed881bb 100644 --- a/src/styles.css +++ b/src/styles.css @@ -28,6 +28,7 @@ main { width: 100%; top: 50%; transform: translateY(-50%); + z-index: 1; } /* ######################################## */ @@ -48,6 +49,10 @@ main { flex: 1; } +.header-line:first-of-type { + justify-content: flex-end; +} + .header-line fieldset { display: flex; gap: 5px; @@ -84,6 +89,21 @@ main { outline: 0; } +button { + background: transparent; + color: #3498db; + border: 1px solid #3498db; + border-radius: 5px; + padding: 10px 20px; + font-family: 'Montserrat', sans-serif; + font-size: 16px; + cursor: pointer; +} + +button:hover { + background-color: #0A0A0A; +} + .logs-label { margin: 0 20px 30px 10px; } @@ -169,3 +189,91 @@ main { #products i { font-size: 20px; } + +/* ######################################## */ +/* # Orders window styling # */ +/* ######################################## */ + +.orders-window { + position: absolute; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: rgba(0, 0, 0, 0.8); + z-index: 2; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + display: none; +} + +.close::after { + position: absolute; + top: 0; + right: 0; + content: "\2716"; + font-size: 30px; + color: white; + line-height: 100px; + text-align: center; + cursor: pointer; + display: flex; + width: 60px; + height: 60px; + justify-content: center; + align-items: center; +} + +.orders-window canvas { + width: 100px; + height: 100px; + border: 1px solid black; + background-color: white; +} + +.line { + display: flex; +} + +.line.gaps > *:not(:first-child) { + margin-left: 10px; +} + +.line .incrementals div { + width: 50px; + height: 50px; + display: flex; + justify-content: center; + align-items: center; + font-size: 30px; +} + +.line .incrementals { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + margin-top: -0.25em; +} + +.incrementals div { + border: 1px solid #3498db; + cursor: pointer; + border-radius: 5px; +} + +.incrementals div:hover { + border: 1px solid #217bb8; +} + +.response { + width: 100%; + flex-grow: 1; + display: flex; + align-items: center; + border-radius: 5px; + border: 1px solid #3498db; + background-color: black; +} \ No newline at end of file diff --git a/src/view/agentController.js b/src/view/agentController.js index 92a98c6..27e9b9c 100644 --- a/src/view/agentController.js +++ b/src/view/agentController.js @@ -1,11 +1,13 @@ class AgentController { constructor(canvas) { // Inicjalizacja + this.canvas = canvas; this.ctx = canvas.getContext('2d'); this.setCanvasSize(canvas); this.ownedProduct = null; this.ownedProductAmount = 0; + this.isDestroyed = false; // Akcje this.actions = { @@ -59,10 +61,18 @@ class AgentController { if (nearestPoint) { if (nearestPoint.action == PATHFINDING_ACTION.MOVE) { - await this.moveInLine(nearestPoint.state.position, nearestPoint.actionCost); + try { + await this.moveInLine(nearestPoint.state.position, nearestPoint.actionCost); + } catch (e) { + return reject(); + } } if (nearestPoint.action == PATHFINDING_ACTION.ROTATE) { - await this.rotate({ z: nearestPoint.state.rotation }, nearestPoint.actionCost); + try { + await this.rotate({ z: nearestPoint.state.rotation }, nearestPoint.actionCost); + } catch (e) { + return reject(); + } } nearestPoint = this.currentPath[++this.currentPathIndex]; @@ -96,6 +106,7 @@ class AgentController { return new Promise((resolve, reject) => { const cycle = () => { if (this.currentAction == this.actions.STATIONARY) return resolve(); + if (this.isDestroyed) return reject(); let { speed } = this; speed *= (actionCost - 1) * 4 + 1; @@ -127,6 +138,11 @@ class AgentController { }); } + async waitFor() { + await waitFor(this.waitTime); + if (this.isDestroyed) throw new Error(); + } + /** * * @param {{ x: number, y: number }} position @@ -176,6 +192,8 @@ class AgentController { })(); const cycle = () => { + if (this.isDestroyed) return reject(); + this.currentAction = this.actions.IN_ROTATION; const oldZRotation = this.rotation.z; @@ -297,11 +315,13 @@ class AgentController { this.ctx.stroke(); } + destroy() { + this.isDestroyed = true; + this.update = () => {}; + } + cancelAllActions() { - this.ownedProduct = Product.REGISTRY.empty; - this.ownedProductAmount = 0; - this.currentPath = []; - this.currentAction = this.actions.STATIONARY; - this.currentPathIndex = 0; + Agent.instance.destroy(); + Agent.instance = new Agent(this.canvas); } } \ No newline at end of file diff --git a/src/view/grid.js b/src/view/grid.js index f90b7cd..6429919 100644 --- a/src/view/grid.js +++ b/src/view/grid.js @@ -1,7 +1,8 @@ const GRID_FIELD_TYPE = Object.freeze({ PATH: 'PATH', SHELF: 'SHELF', - STORAGE: 'STORAGE' + STORAGE: 'STORAGE', + ORDERS: 'ORDERS' }); class Grid { @@ -22,6 +23,7 @@ class Grid { this.drawCostMap(); this.drawShelfs(); this.drawStorages(); + this.drawOrders(); Grid.instance = this; } @@ -52,7 +54,7 @@ class Grid { [...fact(1), GRID_FIELD_TYPE.PATH, ...fact(2), GRID_FIELD_TYPE.PATH, ...fact(2), GRID_FIELD_TYPE.PATH] ); - const lastLine = [...fact(6), GRID_FIELD_TYPE.PATH, ...fact(6, GRID_FIELD_TYPE.STORAGE), GRID_FIELD_TYPE.PATH, ...fact(6)]; + const lastLine = [...fact(6), GRID_FIELD_TYPE.PATH, ...fact(3, GRID_FIELD_TYPE.STORAGE), ...fact(3, GRID_FIELD_TYPE.ORDERS), GRID_FIELD_TYPE.PATH, ...fact(6)]; for (let i = 0; i < 20; i++) { this.grid[i] = [...this.grid[i], lastLine[i]]; } @@ -128,6 +130,35 @@ class Grid { } } + drawOrders () { + let isFirstOfType = true; + let isLastOfType = false; + + let firstX; + for (let [x, line] of this.grid.entries()) { + for (let [y, type] of line.entries()) { + if (type === GRID_FIELD_TYPE.ORDERS) { + if (this.grid[x + 1] && this.grid[x + 1][y] !== GRID_FIELD_TYPE.ORDERS) { + isLastOfType = true; + } + + if (isFirstOfType) { + isFirstOfType = false; + firstX = x; + this.orders(x * 100, y * 100, 100, 100, true, false, true, true); + } else if (isLastOfType) { + isLastOfType = false; + isFirstOfType = true; + this.orders(x * 100, y * 100, 100, 100, true, true, true, false); + this.ordersLabel(firstX * 100, y * 100, (x - firstX + 1) * 100, 100); + } else { + this.orders(x * 100, y * 100, 100, 100, true, false, true, false); + } + } + } + } + } + storeLabel(x, y, w, h) { let fontSize = 40; this.ctx.font = `${fontSize}px Montserrat`; @@ -136,21 +167,46 @@ class Grid { this.ctx.fillText("STORE", x + (w/2), y + (h/2) + 15); } + ordersLabel(x, y, w, h) { + let fontSize = 40; + this.ctx.font = `${fontSize}px Montserrat`; + this.ctx.textAlign = "center"; + this.ctx.fillStyle = '#3498db'; + this.ctx.fillText("ORDERS", x + (w/2), y + (h/2) + 15); + } + shelf (shelf, x, y, w, h) { this.ctx.strokeStyle = '#ffffff'; this.ctx.lineWidth = 5; this.ctx.strokeRect(x, y, w, h); this.drawFixIfOnEdge(x, y, w, h); - var img = new Image(); - img.src = `img/shelves/${shelf.type}.svg`; - img.onload = () => { - this.ctx.drawImage(img, x, y, w, h); + const descriptors = Object.getOwnPropertyDescriptors(SHELF_TYPE); + let imageSource = null; + for (const key of Object.keys(descriptors)) { + if (descriptors[key].value === shelf.type) { + imageSource = key; + } + } + if (imageSource && shelf.type !== SHELF_TYPE.STANDARD_SHELF) { + var img = new Image(); + img.src = `img/shelves/${imageSource}.svg`; + img.onload = () => { + this.ctx.drawImage(img, x, y, w, h); + } } } storage (x, y, w, h, hasTop, hasRight, hasBottom, hasLeft) { - this.ctx.strokeStyle = '#ffff00'; + this.connectedSquare (x, y, w, h, hasTop, hasRight, hasBottom, hasLeft, '#ffff00'); + } + + orders (x, y, w, h, hasTop, hasRight, hasBottom, hasLeft) { + this.connectedSquare (x, y, w, h, hasTop, hasRight, hasBottom, hasLeft, '#3498db'); + } + + connectedSquare (x, y, w, h, hasTop, hasRight, hasBottom, hasLeft, color) { + this.ctx.strokeStyle = color; this.ctx.lineWidth = 5; if (hasTop) { this.ctx.beginPath(); diff --git a/src/view/ordersView.js b/src/view/ordersView.js new file mode 100644 index 0000000..a862dd9 --- /dev/null +++ b/src/view/ordersView.js @@ -0,0 +1,101 @@ +class OrdersView { + + constructor() { + OrdersView.instance = this; + + this.addMultipleCanvases(10); + } + + order() { + const formData = new FormData(); + Promise.all( + [...document.querySelectorAll('.orders-window .canvases canvas')].map((canv, index) => { + return new Promise((resolve, reject) => { + canv.toBlob(blob => { + formData.append(`file-${index}`, blob,`file-${index}.png`); + resolve(); + }); + }); + }) + ).then(async () => { + const response = await fetch('/api/neural', { + method: 'POST', + body: formData + }); + //const json = await response.json(); + console.log(await response.text()); + }); + } + + clear() { + [...document.querySelectorAll('.orders-window .canvases canvas')].forEach(canv => { + canv.getContext('2d').clearRect(0, 0, 100, 100); + }); + } + + openWindow() { + document.querySelector('.orders-window').style.display = 'flex'; + } + + closeWindow() { + document.querySelector('.orders-window').style.display = 'none'; + } + + addMultipleCanvases(amount) { + for (let i = 0; i < amount; i++) { + this.addCanvas(); + } + } + + addCanvas() { + const canv = document.createElement('canvas'); + this.initializeDrawingOnCanvas(canv); + document.querySelector('.orders-window .canvases').append(canv); + } + + removeCanvas() { + const c = document.querySelector('.orders-window .canvases'); + c.removeChild(c.lastChild); + } + + initializeDrawingOnCanvas(canvas) { + const ctx = canvas.getContext("2d"); + let coord = { x: 0, y: 0 }; + + document.addEventListener("mousedown", start); + document.addEventListener("mouseup", stop); + window.addEventListener("resize", resize); + + resize(); + + function resize() { + ctx.canvas.width = 100; + ctx.canvas.height = 100; + } + + function reposition(event) { + coord.x = event.clientX - canvas.offsetLeft; + coord.y = event.clientY - canvas.offsetTop; + } + + function start(event) { + document.addEventListener("mousemove", draw); + reposition(event); + } + + function stop() { + document.removeEventListener("mousemove", draw); + } + + function draw(event) { + ctx.beginPath(); + ctx.lineWidth = 5; + ctx.lineCap = "round"; + ctx.strokeStyle = "#000000"; + ctx.moveTo(coord.x, coord.y); + reposition(event); + ctx.lineTo(coord.x, coord.y); + ctx.stroke(); + } + } +} \ No newline at end of file