diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..c8d4592 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + + { + "type": "pwa-chrome", + "request": "launch", + "name": "Launch Chrome against localhost", + "url": "http://localhost:8080", + "webRoot": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/src/modules/agent.js b/src/modules/agent.js new file mode 100644 index 0000000..e2b5be2 --- /dev/null +++ b/src/modules/agent.js @@ -0,0 +1,13 @@ +class Agent extends AgentController { + constructor(canvas) { + super(canvas); + + let { x, y } = Grid.instance.randomPlace(); + this.moveTo(x, y); + + this.onDestinationReached(() => { + let { x, y } = Grid.instance.randomPlace(); + this.moveTo(x, y); + }) + } +} \ No newline at end of file diff --git a/src/modules/agentController.js b/src/modules/agentController.js new file mode 100644 index 0000000..a6a0501 --- /dev/null +++ b/src/modules/agentController.js @@ -0,0 +1,146 @@ +class AgentController { + constructor(canvas) { + this.ctx = canvas.getContext('2d'); + this.events = { + 'ON_DESTINATION_REACHED': [], + }; + + this.setCanvasSize(canvas); + + this.position = { x: 6, y: 0 }; + + this.update(); + } + + setCanvasSize (canvas) { + canvas.width = 2000; + canvas.height = 900; + } + + update() { + this.clearCanvas(); + if (this.currentPath) { + this.drawPath(this.currentPath); + this.updateAgentPosition(); + } + this.drawAgent(); + + requestAnimationFrame(this.update.bind(this)); + } + + 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) + } + 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) { + 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()); + } + } + } + } + + moveTo(x, y) { + this.currentPath = this.findPathTo(x, y, this.position.x, this.position.y); + } + + 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].x * 100 + 50, path[i].y * 100 + 50); + this.ctx.lineTo(path[i + 1].x * 100 + 50, path[i + 1].y * 100 + 50); + this.ctx.stroke(); + } + } + + 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)); + } + + findPathTo(dx, dy, sx, sy) { + const grid = JSON.parse(JSON.stringify(Grid.instance.grid)); + const queue = []; + const lastPoint = []; + + if (grid[dx][dy] === GRID_FIELD_TYPE.SHELF) { + if(grid[dx][dy + 1] === GRID_FIELD_TYPE.PATH) { + lastPoint.push({ x: dx, y: dy, options: { isShelf: true } }); + dy++; + } else if(grid[dx][dy - 1] === GRID_FIELD_TYPE.PATH) { + lastPoint.push({ x: dx, y: dy, options: { isShelf: true } }); + dy--; + } + } + + queue.push({ x: sx, y: sy, path: [] }); + + while(queue.length) { + const { x, y, path } = queue.shift(); + path.push({ x, y }); + + if (x === dx && y === dy) { + return [...path, ...lastPoint]; + } + if (grid[x][y - 1] === GRID_FIELD_TYPE.PATH) { + queue.push({ x, y: y - 1, path: [...path] }); + } + if (grid[x][y + 1] === GRID_FIELD_TYPE.PATH) { + queue.push({ x, y: y + 1, path: [...path] }); + } + if (grid[x - 1] && grid[x - 1][y] === GRID_FIELD_TYPE.PATH) { + queue.push({ x: x - 1, y, path: [...path] }); + } + if (grid[x + 1] && grid[x + 1][y] === GRID_FIELD_TYPE.PATH) { + queue.push({ x: x + 1, y, path: [...path] }); + } + + grid[x][y] = -1; + } + } + + onDestinationReached(func) { + this.events.ON_DESTINATION_REACHED.push(func); + } + + 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(); + } +} \ No newline at end of file diff --git a/src/modules/floor.js b/src/modules/floor.js new file mode 100644 index 0000000..2c965c9 --- /dev/null +++ b/src/modules/floor.js @@ -0,0 +1,45 @@ +class Floor { + constructor(canvas) { + this.ctx = canvas.getContext('2d'); + this.setCanvasSize(canvas); + this.drawFloor(); + } + + setCanvasSize (canvas) { + canvas.width = 2000; + canvas.height = 900; + } + + drawFloor() { + let tileWidth = 50; + let tileHeight = 50; + + for (let x = 0; x < 2000; x += tileWidth) { + for (let y = 0; y < 900; y += tileHeight) { + this.tile(x, y, tileWidth, tileHeight); + } + } + } + + tile(x, y, w, h) { + this.ctx.strokeStyle = '#202020'; + this.ctx.strokeRect(x, y, w, h); + this.roundRect(x, y, w, h); + } + + roundRect( x, y, w, h, r = 20) { + this.ctx.strokeStyle = '#202020'; + 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(); + } +} diff --git a/src/modules/grid.js b/src/modules/grid.js new file mode 100644 index 0000000..f15ea2a --- /dev/null +++ b/src/modules/grid.js @@ -0,0 +1,171 @@ +const GRID_FIELD_TYPE = Object.freeze({ + PATH: 0, + SHELF: 1, + STORAGE: 2 +}); + +class Grid { + static instance; + + constructor(canvas) { + this.grid = [[]]; + this.ctx; + + this.ctx = canvas.getContext('2d'); + + this.setCanvasSize(canvas); + this.processGrid(); + this.drawShelfs(); + this.drawStorages(); + + Grid.instance = this; + } + + getGrid () { + return this.grid; + } + + setCanvasSize (canvas) { + canvas.width = 2000; + canvas.height = 900; + } + + processGrid () { + const fact = (a, value = GRID_FIELD_TYPE.SHELF) => Array(a).fill(value, 0, a); + const cartesian = (arr1, arr2) => arr1.map(a => (a && arr2) || fact(arr2.length, 0)); + + this.grid = cartesian( + [...fact(6), GRID_FIELD_TYPE.PATH, ...fact(6), GRID_FIELD_TYPE.PATH, ...fact(6)], + [...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)]; + for (let i = 0; i < 20; i++) { + this.grid[i] = [...this.grid[i], lastLine[i]]; + } + } + + drawShelfs () { + for (let [x, line] of this.grid.entries()) { + for (let [y, type] of line.entries()) { + if (type === GRID_FIELD_TYPE.SHELF) { + this.shelf(x * 100, y * 100, 100, 100); + } + } + } + } + + randomPlace() { + 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) { + return { x, y }; + } + return this.randomPlace(); + } + + drawStorages () { + 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.STORAGE) { + if (this.grid[x + 1] && this.grid[x + 1][y] !== GRID_FIELD_TYPE.STORAGE) { + isLastOfType = true; + } + + if (isFirstOfType) { + isFirstOfType = false; + firstX = x; + this.storage(x * 100, y * 100, 100, 100, true, false, true, true); + } else if (isLastOfType) { + isLastOfType = false; + isFirstOfType = true; + this.storage(x * 100, y * 100, 100, 100, true, true, true, false); + this.storeLabel(firstX * 100, y * 100, (x - firstX + 1) * 100, 100); + } else { + this.storage(x * 100, y * 100, 100, 100, true, false, true, false); + } + } + } + } + } + + storeLabel(x, y, w, h) { + let fontSize = 40; + this.ctx.font = `${fontSize}px Montserrat`; + this.ctx.textAlign = "center"; + this.ctx.fillStyle = 'yellow'; + this.ctx.fillText("STORE", x + (w/2), y + (h/2) + 15); + } + + 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); + } + + storage (x, y, w, h, hasTop, hasRight, hasBottom, hasLeft) { + this.ctx.strokeStyle = '#ffff00'; + this.ctx.lineWidth = 5; + if (hasTop) { + this.ctx.beginPath(); + this.ctx.moveTo(x, y); + this.ctx.lineTo(x + w, y); + this.ctx.stroke(); + } + if (hasRight) { + this.ctx.beginPath(); + this.ctx.moveTo(x + w, y); + this.ctx.lineTo(x + w, y + h); + this.ctx.stroke(); + } + if (hasBottom) { + this.ctx.beginPath(); + this.ctx.moveTo(x, y + h); + this.ctx.lineTo(x + w, y + h); + this.ctx.stroke(); + } + if (hasLeft) { + this.ctx.beginPath(); + this.ctx.moveTo(x, y); + this.ctx.lineTo(x, y + h); + this.ctx.stroke(); + } + this.drawFixIfOnEdge(x, y, w, h); + } + + drawFixIfOnEdge(x, y, w, h) { + const offsetFix = 2; + + if (x === 0) { + this.ctx.beginPath(); + this.ctx.moveTo(x + offsetFix, y); + this.ctx.lineTo(x + offsetFix, y + h); + this.ctx.stroke(); + } else if (x + w === 2000) { + this.ctx.beginPath(); + this.ctx.moveTo(x + w - offsetFix, y); + this.ctx.lineTo(x + w - offsetFix, y + h); + this.ctx.stroke(); + } + + if (y === 0) { + this.ctx.beginPath(); + this.ctx.moveTo(x, y + offsetFix); + this.ctx.lineTo(x + w, y + offsetFix); + this.ctx.stroke(); + } else if (y + h === 900) { + this.ctx.beginPath(); + this.ctx.moveTo(x, y + h - offsetFix); + this.ctx.lineTo(x + w, y + h - offsetFix); + this.ctx.stroke(); + } + } + +} + diff --git a/src/modules/log.js b/src/modules/log.js new file mode 100644 index 0000000..45bf6e3 --- /dev/null +++ b/src/modules/log.js @@ -0,0 +1,7 @@ +class log { + +} + +function add (str) { + return str; +} \ No newline at end of file diff --git a/src/modules/products.js b/src/modules/products.js new file mode 100644 index 0000000..2e04b54 --- /dev/null +++ b/src/modules/products.js @@ -0,0 +1,94 @@ +class Products { + + constructor(canvas){ + + this.gridProducts = [ + ['a','b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'], + ['a','b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'], + ['a','b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'], + ['a','b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'], + ['a','b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'], + ['a','b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'], + ['a','b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'], + ['a','b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'], + ['a','b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'], + ['a','b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'], + ['a','b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'], + ['a','b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'], + ['a','b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'], + ['a','b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'], + ['a','b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'], + ['a','b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'], + ['a','b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'], + ['a','b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'], + ['a','b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'], + ['a','b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'], + ['a','b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'] + ]; + this.ctx = canvas.getContext('2d'); + this.setCanvasSize(canvas); + this.update(); + + } + + setCanvasSize (canvas) { + canvas.width = 2000; + canvas.height = 900; + } + + update(){ + this.clearCanvas(); + this.drawProducts(); + } + + + + drawProducts () { + for (let [x, line] of Grid.instance.grid.entries()) { + for (let [y, type] of line.entries()) { + if (type === GRID_FIELD_TYPE.SHELF) { + let v =this.value(x * 100, y * 100); + this.product(x * 100, y * 100, v, x, y); + } + } + } + } + + value (x,y){ + let fontSize = 20; + this.ctx.font = `${fontSize}px Montserrat`; + this.ctx.textAlign = "left"; + this.ctx.fillStyle = 'white'; + let number = Math.floor(Math.random() * (50 - 0 + 1)) + 0; + this.ctx.fillText(number, x+10, y+90); + return number; + } + + product(x, y, v, productsKey, productsValue){ + let fontSize = 40; + this.ctx.font = `${fontSize}px Montserrat`; + this.ctx.textAlign = "center"; + if (v <= 10){ + this.ctx.fillStyle = '#ff0000'; + } + else if(v <= 25){ + this.ctx.fillStyle = '#ffe700'; + } + else { + this.ctx.fillStyle = '#74ee15'; + } + let productsColumn = this.gridProducts[productsKey]; + let prdct = productsColumn[productsValue]; + this.ctx.fillText(prdct, x+50, y+60); + } + + + changeValue(xFirst, yFirst, xSecond, ySecond){ + + } + + clearCanvas() { + this.ctx.clearRect(0, 0, 2000, 900); + } + +} \ No newline at end of file diff --git a/src/utilities.js b/src/utilities.js index 9aa390e..174e043 100644 --- a/src/utilities.js +++ b/src/utilities.js @@ -5,7 +5,7 @@ * @param {...any[]} rows * @returns */ -function fromTable(header, ...rows) { + function fromTable(header, ...rows) { function tupleToRecord(names, array) { return names.reduce((p, v, i) => ({ ...p, [v]: array[i] }), {}) } @@ -64,6 +64,12 @@ function randomFromSet(set) { unreachable(); } +function mapToJson(map) { + return JSON.stringify([...map]); +} +function jsonToMap(jsonStr) { + return new Map(JSON.parse(jsonStr)); +} function nice(v) { return `${(v * 100).toFixed(1)}%` } @@ -75,7 +81,9 @@ async function requestJSONCached(url, params = {}) { const response = await fetch(url, params); const json = await response.json(); requestJSONCached.cache.set(key, json); + const cache = mapToJson(requestJSONCached.cache); + localStorage.setItem("cache", cache); return json; } -requestJSONCached.cache = new Map(); \ No newline at end of file +requestJSONCached.cache = jsonToMap(localStorage.getItem("cache"));