feat: Agent rotation, fix: Product icons

This commit is contained in:
Marcin Czerniak 2021-04-18 18:58:27 +02:00
parent cee48874c5
commit ef225fc850
8 changed files with 291 additions and 63 deletions

View File

@ -7,14 +7,16 @@
<!-- Styles --> <!-- Styles -->
<link rel="preconnect" href="https://fonts.gstatic.com"> <link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@100;400&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@100;400&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.13.0/css/all.css">
<link rel="stylesheet" href="styles.css"> <link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="fontawesome/css/all.min.css"> <!-- <link rel="stylesheet" href="fontawesome/css/all.min.css"> -->
<!-- Modules --> <!-- Modules -->
<script src="modules/configuration.js"></script> <script src="modules/configuration.js"></script>
<script src="modules/utilities.js"></script> <script src="modules/utilities.js"></script>
<script src="modules/product.js"></script>
<script src="modules/floor.js"></script> <script src="modules/floor.js"></script>
<script src="modules/grid.js"></script> <script src="modules/grid.js"></script>

View File

@ -1,4 +1,4 @@
window.addEventListener('DOMContentLoaded', () => { window.addEventListener('DOMContentLoaded', () => document.fonts.ready.then(() => {
const floorCanvas = document.getElementById('canvas-floor'); const floorCanvas = document.getElementById('canvas-floor');
const gridCanvas = document.getElementById('canvas-grid'); const gridCanvas = document.getElementById('canvas-grid');
const agentCanvas = document.getElementById('canvas-agent'); const agentCanvas = document.getElementById('canvas-agent');
@ -8,4 +8,4 @@ window.addEventListener('DOMContentLoaded', () => {
const grid = new Grid(gridCanvas); const grid = new Grid(gridCanvas);
const agent = new Agent(agentCanvas); const agent = new Agent(agentCanvas);
const products = new Products(productsCanvas); const products = new Products(productsCanvas);
}); }));

View File

@ -2,12 +2,47 @@ class Agent extends AgentController {
constructor(canvas) { constructor(canvas) {
super(canvas); super(canvas);
let { x, y } = Grid.instance.randomPlace(); const cycle = async () => {
this.moveTo(x, y);
this.onDestinationReached(() => { await this.takeFromStore(Product.RANDOM_FROM_REGISTRY(), Math.floor(Math.random() * 40) + 10);
let { x, y } = Grid.instance.randomPlace(); const randomPlace = Grid.instance.randomPlace();
this.moveTo(x, y); 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;
} }
} }

View File

@ -1,14 +1,25 @@
class AgentController { class AgentController {
constructor(canvas) { constructor(canvas) {
// Inicjalizacja
this.ctx = canvas.getContext('2d'); this.ctx = canvas.getContext('2d');
this.events = {
'ON_DESTINATION_REACHED': [],
};
this.setCanvasSize(canvas); 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(); this.update();
} }
@ -21,7 +32,6 @@ class AgentController {
this.clearCanvas(); this.clearCanvas();
if (this.currentPath) { if (this.currentPath) {
this.drawPath(this.currentPath); this.drawPath(this.currentPath);
this.updateAgentPosition();
} }
this.drawAgent(); this.drawAgent();
@ -29,35 +39,141 @@ class AgentController {
} }
updateAgentPosition() { updateAgentPosition() {
const speed = 30; return new Promise((resolve, reject) => {
if (!this.currentPathIndex) { const cycle = async () => {
this.currentPathIndex = 0; if (!this.currentPathIndex) {
} this.currentPathIndex = 0;
}
let nearestPoint = this.currentPath[this.currentPathIndex];
let nearestPoint = this.currentPath[this.currentPathIndex];
if (nearestPoint) {
this.position = { if (nearestPoint) {
x: this.position.x + (Math.sign(nearestPoint.x - this.position.x)/speed), const requiredRotation = this.findRequiredRotation(nearestPoint);
y: this.position.y + (Math.sign(nearestPoint.y - this.position.y)/speed) 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]; cycle();
if (!nearestPoint) { })
}
/**
*
* @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 = { this.position = {
x: Math.round(this.position.x), x: Math.round(this.position.x),
y: Math.round(this.position.y) y: Math.round(this.position.y)
} }
this.currentPath = []; resolve();
this.currentPathIndex = 0; } else {
this.events.ON_DESTINATION_REACHED.forEach(func => func()); 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) { moveTo(x, y) {
this.currentPath = this.findPathTo(x, y, this.position.x, this.position.y); this.currentPath = this.findPathTo(x, y, this.position.x, this.position.y);
console.log(this.currentPath);
return this.updateAgentPosition();
} }
drawPath(path) { drawPath(path) {
@ -74,8 +190,43 @@ class AgentController {
drawAgent() { drawAgent() {
const {x, y} = this.position; const {x, y} = this.position;
const offset = 5; const offset = 5;
this.ctx.clearRect((x * 100) + (offset), (y * 100) + (offset), 100 - (offset * 2), 100 - (offset * 2)); const rotation = this.rotation.z;
this.roundRect((x * 100) + (offset), (y * 100) + (offset), 100 - (offset * 2), 100 - (offset * 2));
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) { findPathTo(dx, dy, sx, sy) {
@ -119,10 +270,6 @@ class AgentController {
} }
} }
onDestinationReached(func) {
this.events.ON_DESTINATION_REACHED.push(func);
}
clearCanvas() { clearCanvas() {
this.ctx.clearRect(0, 0, 2000, 900); this.ctx.clearRect(0, 0, 2000, 900);
} }

View File

@ -59,7 +59,7 @@ class Grid {
const x = Math.floor(Math.random() * this.grid.length); const x = Math.floor(Math.random() * this.grid.length);
const y = Math.floor(Math.random() * this.grid[0].length); const y = Math.floor(Math.random() * this.grid[0].length);
const rand = this.grid[x][y]; 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 { x, y };
} }
return this.randomPlace(); return this.randomPlace();

29
src/modules/product.js Normal file
View File

@ -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))]];
}
}

View File

@ -2,27 +2,27 @@ class Products {
constructor(canvas) { constructor(canvas) {
this.gridProducts = [ this.gridProducts = [
['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()],
['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()],
['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()],
['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()],
['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()],
['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()],
['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()],
['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()],
['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()],
['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()],
['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()],
['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()],
['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()],
['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()],
['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()],
['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()],
['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()],
['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()],
['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()]
]; ];
this.gridProductsAmount = [] this.gridProductsAmount = []
@ -76,7 +76,7 @@ class Products {
product(x, y, v, productsKey, productsValue) { product(x, y, v, productsKey, productsValue) {
let fontSize = 40; let fontSize = 40;
this.ctx.font = `${fontSize}px Montserrat`; this.ctx.font = `900 ${fontSize}px "Font Awesome 5 Free"`;
this.ctx.textAlign = "center"; this.ctx.textAlign = "center";
if (v <= 10){ if (v <= 10){
this.ctx.fillStyle = '#ff0000'; this.ctx.fillStyle = '#ff0000';
@ -89,7 +89,7 @@ class Products {
} }
let productsColumn = this.gridProducts[productsKey]; let productsColumn = this.gridProducts[productsKey];
let prdct = productsColumn[productsValue]; let prdct = productsColumn[productsValue];
this.ctx.fillText(prdct, x+50, y+60); this.ctx.fillText(prdct.icon, x+50, y+60);
} }
clearCanvas() { clearCanvas() {
@ -102,4 +102,8 @@ class Products {
count: this.gridProductsAmount[x][y] count: this.gridProductsAmount[x][y]
} }
} }
setProductAt(product, count, x, y) {
// TODO: Implement this function
}
} }

View File

@ -21,3 +21,14 @@ function fromTable(header, ...rows) {
function clamp(v, lo, hi) { function clamp(v, lo, hi) {
return v <= lo ? lo : v >= hi ? hi : v 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);
})
}