class AgentController { constructor(canvas) { // Inicjalizacja this.ctx = canvas.getContext('2d'); this.setCanvasSize(canvas); 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 = 90 - AgentMoveSpeed; this.rotationSpeed = 90 / ( 90 - AgentRotationSpeed); this.update(); } setCanvasSize (canvas) { canvas.width = 2000; canvas.height = 900; } update() { this.clearCanvas(); if (this.currentPath) { this.drawPath(this.currentPath); } this.drawAgent(); requestAnimationFrame(this.update.bind(this)); } updateAgentPosition() { return new Promise((resolve, reject) => { const cycle = async () => { if (!this.currentPathIndex) { this.currentPathIndex = 0; } let nearestPoint = this.currentPath[this.currentPathIndex]; if (nearestPoint) { if (nearestPoint.action == PATHFINDING_ACTION.MOVE) { await this.moveInLine(nearestPoint.state.position); } if (nearestPoint.action == PATHFINDING_ACTION.ROTATE) { await this.rotate({ z: nearestPoint.state.rotation }); } nearestPoint = this.currentPath[++this.currentPathIndex]; if (!nearestPoint) { this.currentPath = []; this.currentPathIndex = 0; this.currentAction = this.actions.STATIONARY; resolve(); } else { cycle(); } } else { resolve(); } } 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) } 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) => { 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) < 5) { 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); return this.updateAgentPosition(); } drawPath(path) { for(let i = 0; i < path.length - 1; i++) { this.ctx.beginPath(); this.ctx.strokeWidth = 5; this.ctx.strokeStyle = 'red'; this.ctx.moveTo(path[i].state.position.x * 100 + 50, path[i].state.position.y * 100 + 50); this.ctx.lineTo(path[i + 1].state.position.x * 100 + 50, path[i + 1].state.position.y * 100 + 50); this.ctx.stroke(); } } drawAgent() { const {x, y} = this.position; const offset = 5; 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) { if (Grid.instance.getGrid()[dx][dy] == GRID_FIELD_TYPE.SHELF || Grid.instance.getGrid()[dx][dy] == GRID_FIELD_TYPE.STORAGE) { if (Grid.instance.getGrid()[dx][dy - 1] == GRID_FIELD_TYPE.PATH) { return Pathfinding.search({ position: { x: sx, y: sy }, rotation: this.rotation.z }, { position: { x: dx, y: dy - 1 }, rotation: 180 }); } return Pathfinding.search({ position: { x: sx, y: sy }, rotation: this.rotation.z }, { position: { x: dx, y: dy + 1 }, rotation: 0 }); } return Pathfinding.search({ position: { x: sx, y: sy }, rotation: this.rotation.z }, { position: { x: dx, y: dy }, rotation: this.rotation.z }); } clearCanvas() { this.ctx.clearRect(0, 0, 2000, 900); } roundRect(x, y, w, h, r = 20) { this.ctx.lineWidth = 5; this.ctx.strokeStyle = 'red'; this.ctx.beginPath(); this.ctx.moveTo(x + r, y); this.ctx.lineTo(x + w - r, y); this.ctx.quadraticCurveTo(x + w, y, x + w, y + r); this.ctx.lineTo(x + w, y + h - r); this.ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h); this.ctx.lineTo(x + r, y + h); this.ctx.quadraticCurveTo(x, y + h, x, y + h - r); this.ctx.lineTo(x, y + r); this.ctx.quadraticCurveTo(x, y, x + r, y); this.ctx.closePath(); this.ctx.stroke(); } }