From ef225fc850bd45f1bab33b8170b026618f6b888a Mon Sep 17 00:00:00 2001 From: Marcin Czerniak Date: Sun, 18 Apr 2021 18:58:27 +0200 Subject: [PATCH] feat: Agent rotation, fix: Product icons --- src/index.html | 6 +- src/main.js | 4 +- src/modules/agent.js | 47 +++++++- src/modules/agentController.js | 205 ++++++++++++++++++++++++++++----- src/modules/grid.js | 2 +- src/modules/product.js | 29 +++++ src/modules/products.js | 50 ++++---- src/modules/utilities.js | 11 ++ 8 files changed, 291 insertions(+), 63 deletions(-) create mode 100644 src/modules/product.js diff --git a/src/index.html b/src/index.html index 9fad841..2546a6c 100644 --- a/src/index.html +++ b/src/index.html @@ -7,14 +7,16 @@ - + + - + + diff --git a/src/main.js b/src/main.js index 3b9c66a..b253951 100644 --- a/src/main.js +++ b/src/main.js @@ -1,4 +1,4 @@ -window.addEventListener('DOMContentLoaded', () => { +window.addEventListener('DOMContentLoaded', () => document.fonts.ready.then(() => { const floorCanvas = document.getElementById('canvas-floor'); const gridCanvas = document.getElementById('canvas-grid'); const agentCanvas = document.getElementById('canvas-agent'); @@ -8,4 +8,4 @@ window.addEventListener('DOMContentLoaded', () => { const grid = new Grid(gridCanvas); const agent = new Agent(agentCanvas); const products = new Products(productsCanvas); -}); \ No newline at end of file +})); \ No newline at end of file diff --git a/src/modules/agent.js b/src/modules/agent.js index e2b5be2..3cb730e 100644 --- a/src/modules/agent.js +++ b/src/modules/agent.js @@ -2,12 +2,47 @@ class Agent extends AgentController { constructor(canvas) { super(canvas); - let { x, y } = Grid.instance.randomPlace(); - this.moveTo(x, y); + const cycle = async () => { - this.onDestinationReached(() => { - let { x, y } = Grid.instance.randomPlace(); - this.moveTo(x, y); - }) + await this.takeFromStore(Product.RANDOM_FROM_REGISTRY(), Math.floor(Math.random() * 40) + 10); + const randomPlace = Grid.instance.randomPlace(); + await this.deliver(randomPlace.x, randomPlace.y); + + await waitFor(2000); + cycle(); + }; + + cycle(); + + } + + /** + * Nakaż agentowi przejść na wybraną pozycję + * @param {number} x Współrzędna x + * @param {number} y Współrzędna y + */ + moveTo(x, y) { + return super.moveTo(x, y); + } + + /** + * Nakaż agentowi dostarczyć produkt, który aktualnie niesie na wybraną pozycję + * @param {number} x Współrzędna x + * @param {number} y Współrzędna y + */ + async deliver(x, y) { + await this.moveTo(x, y); + this.ownedProductAmount = 0; + this.ownedProduct = Product.REGISTRY.empty; + } + + /** + * + * @param {*} product + */ + async takeFromStore(product, amount) { + await this.moveTo(6, 8); + this.ownedProductAmount = amount; + this.ownedProduct = product; } } \ No newline at end of file diff --git a/src/modules/agentController.js b/src/modules/agentController.js index a6a0501..8502906 100644 --- a/src/modules/agentController.js +++ b/src/modules/agentController.js @@ -1,14 +1,25 @@ class AgentController { constructor(canvas) { + // Inicjalizacja this.ctx = canvas.getContext('2d'); - this.events = { - 'ON_DESTINATION_REACHED': [], - }; - this.setCanvasSize(canvas); - this.position = { x: 6, y: 0 }; + this.ownedProduct = Product.REGISTRY.empty; + this.ownedProductAmount = 0; + // Akcje + this.actions = { + 'STATIONARY': 0, + 'IN_MOVE': 1, + 'IN_ROTATION': 2, + } + this.currentAction = this.actions.STATIONARY; + + // Pozycja i poruszanie się + this.position = { x: 6, y: 0 }; + this.rotation = { z: 0 }; + this.speed = 30; + this.rotationSpeed = 2; this.update(); } @@ -21,7 +32,6 @@ class AgentController { this.clearCanvas(); if (this.currentPath) { this.drawPath(this.currentPath); - this.updateAgentPosition(); } this.drawAgent(); @@ -29,35 +39,141 @@ class AgentController { } updateAgentPosition() { - const speed = 30; - if (!this.currentPathIndex) { - this.currentPathIndex = 0; - } - - let nearestPoint = this.currentPath[this.currentPathIndex]; - - if (nearestPoint) { - this.position = { - x: this.position.x + (Math.sign(nearestPoint.x - this.position.x)/speed), - y: this.position.y + (Math.sign(nearestPoint.y - this.position.y)/speed) + return new Promise((resolve, reject) => { + const cycle = async () => { + if (!this.currentPathIndex) { + this.currentPathIndex = 0; + } + + let nearestPoint = this.currentPath[this.currentPathIndex]; + + if (nearestPoint) { + const requiredRotation = this.findRequiredRotation(nearestPoint); + await this.rotate({ z: requiredRotation }); + if (!nearestPoint.options || !nearestPoint.options.isShelf) { + await this.moveInLine(nearestPoint); + } + + nearestPoint = this.currentPath[++this.currentPathIndex]; + if (!nearestPoint) { + this.currentPath = []; + this.currentPathIndex = 0; + this.currentAction = this.actions.STATIONARY; + resolve(); + } else { + cycle(); + } + } } - if (Math.abs(nearestPoint.x - this.position.x) < 0.001 && Math.abs(nearestPoint.y - this.position.y) < 0.001) { - nearestPoint = this.currentPath[++this.currentPathIndex]; - if (!nearestPoint) { + + cycle(); + }) + } + + /** + * + * @param {{ x: number, y: number }} position + */ + moveInLine(position) { + return new Promise((resolve, reject) => { + const cycle = () => { + const { speed } = this; + this.currentAction = this.actions.IN_MOVE; + this.position = { + x: this.position.x + (Math.sign(position.x - this.position.x)/speed), + y: this.position.y + (Math.sign(position.y - this.position.y)/speed) + } + + if (Math.abs(position.x - this.position.x) < 0.001 && Math.abs(position.y - this.position.y) < 0.001) { this.position = { x: Math.round(this.position.x), y: Math.round(this.position.y) } - this.currentPath = []; - this.currentPathIndex = 0; - this.events.ON_DESTINATION_REACHED.forEach(func => func()); + resolve(); + } else { + requestAnimationFrame(() => cycle()); } } + + cycle(); + }); + } + + /** + * + * @param {{ x: number, y: number }} position + */ + findRequiredRotation(position) { + if (position.x == this.position.x && position.y == this.position.y) { + return this.rotation.z; + } + + const compare = (a, b) => { + return Math.abs(a - b) < 1; + } + + if (compare(this.position.x, position.x)) { + if (this.position.y < position.y) { + return 180; + } else { + return 0; + } + } else { + if (this.position.x < position.x) { + return 90; + } else { + return 270; + } } } + /** + * + * @param {{ z: number}} rotation + * @returns + */ + rotate(rotation) { + return new Promise((resolve, reject) => { + // console.log(this.rotation); // 270 + // console.log(rotation); // 0 + + if (this.rotation.z == rotation.z) { + resolve(); + } + + const sign = (() => { + if (((this.rotation.z > rotation.z) || (this.rotation.z == 0 && rotation.z == 270)) && !(this.rotation.z == 270 && rotation.z == 0)) { + return -1; + } + return 1; + })(); + + const cycle = () => { + this.currentAction = this.actions.IN_ROTATION; + + const oldZRotation = this.rotation.z; + const newZRotation = rotation.z; + + const clampRotation = (rot) => (rot + 360) % 360; + + this.rotation = { z: clampRotation(oldZRotation + (this.rotationSpeed * sign)) }; + + if (Math.abs(oldZRotation - newZRotation) < 1) { + resolve(); + this.rotation = { z: clampRotation(newZRotation) } + } else { + requestAnimationFrame(() => cycle()); + } + } + + cycle(); + }); + } + moveTo(x, y) { this.currentPath = this.findPathTo(x, y, this.position.x, this.position.y); + console.log(this.currentPath); + return this.updateAgentPosition(); } drawPath(path) { @@ -74,8 +190,43 @@ class AgentController { drawAgent() { const {x, y} = this.position; const offset = 5; - this.ctx.clearRect((x * 100) + (offset), (y * 100) + (offset), 100 - (offset * 2), 100 - (offset * 2)); - this.roundRect((x * 100) + (offset), (y * 100) + (offset), 100 - (offset * 2), 100 - (offset * 2)); + const rotation = this.rotation.z; + + const newX = (x * 100) + (offset); + const newY = (y * 100) + (offset); + const w = 100 - (offset * 2); + const h = 100 - (offset * 2); + + this.ctx.translate(newX + (w / 2), newY + (h / 2)); + this.ctx.rotate(rotation * Math.PI / 180); + + // Base shape + this.ctx.clearRect(-(w / 2), -(h / 2), w, h); + this.roundRect(-(w / 2), -(h / 2), w, h); + + // Eyes + // this.roundRect(10 - (w / 2), 10 - (h / 2), w / 4, h / 4); + // this.roundRect(80 - (3 * w / 4), 10 - (h / 2), w / 4, h / 4); + this.ctx.fillStyle = 'red'; + this.ctx.fillRect(-(w/2) + 20, (-h/2) - 10, w - 40, 10); + + this.ctx.rotate(-rotation * Math.PI / 180); + + // Product + this.ctx.font = '900 40px "Font Awesome 5 Free"'; + this.ctx.textAlign = 'center'; + this.ctx.fillStyle = 'white'; + this.ctx.fillText(this.ownedProduct.icon, 0, 15); + + // Amount + if (this.ownedProductAmount) { + this.ctx.font = '20px Montserrat'; + this.ctx.textAlign = 'left'; + this.ctx.fillStyle = 'white'; + this.ctx.fillText(this.ownedProductAmount, 10, 35); + } + + this.ctx.translate(-(newX + (w / 2)), -(newY + (h / 2))); } findPathTo(dx, dy, sx, sy) { @@ -119,10 +270,6 @@ class AgentController { } } - onDestinationReached(func) { - this.events.ON_DESTINATION_REACHED.push(func); - } - clearCanvas() { this.ctx.clearRect(0, 0, 2000, 900); } diff --git a/src/modules/grid.js b/src/modules/grid.js index f15ea2a..5b4f86d 100644 --- a/src/modules/grid.js +++ b/src/modules/grid.js @@ -59,7 +59,7 @@ class Grid { const x = Math.floor(Math.random() * this.grid.length); const y = Math.floor(Math.random() * this.grid[0].length); const rand = this.grid[x][y]; - if (rand === GRID_FIELD_TYPE.SHELF || rand === GRID_FIELD_TYPE.PATH) { + if (rand === GRID_FIELD_TYPE.SHELF) { return { x, y }; } return this.randomPlace(); diff --git a/src/modules/product.js b/src/modules/product.js new file mode 100644 index 0000000..0fec2f4 --- /dev/null +++ b/src/modules/product.js @@ -0,0 +1,29 @@ +class Product { + /** + * Stwórz produkt o określonej nazwie i ikonie + * @param {string} name + * @param {string} icon + * @example const product = new Product('apple', 'fa-apple') + */ + constructor(name, icon) { + this.name = name; + this.icon = icon; + } + + static get REGISTRY() { + return { + empty: new Product('', ''), + apple: new Product('apple', '\uf5d1'), + bacon: new Product('bacon', '\uf7e5'), + bone: new Product('bone', '\uf5d7'), + bread: new Product('bread', '\uf7ec'), + candyCane: new Product('candy cane', '\uf786'), + carrot: new Product('carrot', '\uf787'), + cheese: new Product('cheese', '\uf7ef') + }; + } + + 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))]]; + } +} \ No newline at end of file diff --git a/src/modules/products.js b/src/modules/products.js index c5d8939..d7c632a 100644 --- a/src/modules/products.js +++ b/src/modules/products.js @@ -2,27 +2,27 @@ class Products { constructor(canvas) { this.gridProducts = [ - ['a','', 'c', 'd', '', 'f', 'g', '', 'i'], - ['a','', 'c', 'd', '', 'f', 'g', '', 'i'], - ['a','', 'c', 'd', '', 'f', 'g', '', 'i'], - ['a','', 'c', 'd', '', 'f', 'g', '', 'i'], - ['a','', 'c', 'd', '', 'f', 'g', '', 'i'], - ['a','', 'c', 'd', '', 'f', 'g', '', 'i'], - ['a','', 'c', 'd', '', 'f', 'g', '', 'i'], - ['a','', 'c', 'd', '', 'f', 'g', '', 'i'], - ['a','', 'c', 'd', '', 'f', 'g', '', 'i'], - ['a','', 'c', 'd', '', 'f', 'g', '', 'i'], - ['a','', 'c', 'd', '', 'f', 'g', '', 'i'], - ['a','', 'c', 'd', '', 'f', 'g', '', 'i'], - ['a','', 'c', 'd', '', 'f', 'g', '', 'i'], - ['a','', 'c', 'd', '', 'f', 'g', '', 'i'], - ['a','', 'c', 'd', '', 'f', 'g', '', 'i'], - ['a','', 'c', 'd', '', 'f', 'g', '', 'i'], - ['a','', 'c', 'd', '', 'f', 'g', '', 'i'], - ['a','', 'c', 'd', '', 'f', 'g', '', 'i'], - ['a','', 'c', 'd', '', 'f', 'g', '', 'i'], - ['a','', 'c', 'd', '', 'f', 'g', '', 'i'], - ['a','', 'c', 'd', '', 'f', 'g', '', 'i'] + [Product.RANDOM_FROM_REGISTRY(),'', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY()], + [Product.RANDOM_FROM_REGISTRY(),'', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY()], + [Product.RANDOM_FROM_REGISTRY(),'', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY()], + [Product.RANDOM_FROM_REGISTRY(),'', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY()], + [Product.RANDOM_FROM_REGISTRY(),'', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY()], + [Product.RANDOM_FROM_REGISTRY(),'', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY()], + ['','', '', '', '', '', '', '', ''], + [Product.RANDOM_FROM_REGISTRY(),'', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY()], + [Product.RANDOM_FROM_REGISTRY(),'', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY()], + [Product.RANDOM_FROM_REGISTRY(),'', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY()], + [Product.RANDOM_FROM_REGISTRY(),'', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY()], + [Product.RANDOM_FROM_REGISTRY(),'', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY()], + [Product.RANDOM_FROM_REGISTRY(),'', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY()], + ['','', '', '', '', '', '', '', ''], + [Product.RANDOM_FROM_REGISTRY(),'', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY()], + [Product.RANDOM_FROM_REGISTRY(),'', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY()], + [Product.RANDOM_FROM_REGISTRY(),'', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY()], + [Product.RANDOM_FROM_REGISTRY(),'', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY()], + [Product.RANDOM_FROM_REGISTRY(),'', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY()], + [Product.RANDOM_FROM_REGISTRY(),'', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY()], + [Product.RANDOM_FROM_REGISTRY(),'', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY(), Product.RANDOM_FROM_REGISTRY(), '', Product.RANDOM_FROM_REGISTRY()] ]; this.gridProductsAmount = [] @@ -76,7 +76,7 @@ class Products { product(x, y, v, productsKey, productsValue) { let fontSize = 40; - this.ctx.font = `${fontSize}px Montserrat`; + this.ctx.font = `900 ${fontSize}px "Font Awesome 5 Free"`; this.ctx.textAlign = "center"; if (v <= 10){ this.ctx.fillStyle = '#ff0000'; @@ -89,7 +89,7 @@ class Products { } let productsColumn = this.gridProducts[productsKey]; let prdct = productsColumn[productsValue]; - this.ctx.fillText(prdct, x+50, y+60); + this.ctx.fillText(prdct.icon, x+50, y+60); } clearCanvas() { @@ -102,4 +102,8 @@ class Products { count: this.gridProductsAmount[x][y] } } + + setProductAt(product, count, x, y) { + // TODO: Implement this function + } } \ No newline at end of file diff --git a/src/modules/utilities.js b/src/modules/utilities.js index d3fa400..142e48d 100644 --- a/src/modules/utilities.js +++ b/src/modules/utilities.js @@ -21,3 +21,14 @@ function fromTable(header, ...rows) { function clamp(v, lo, hi) { return v <= lo ? lo : v >= hi ? hi : v } + +/** + * Async timeout function + * @param {number} miliseconds Number of miliseconds to wait + * @returns + */ +function waitFor(miliseconds) { + return new Promise((resolve, reject) => { + setTimeout(() => resolve(), miliseconds); + }) +}