Merge branch 'main' of https://git.wmi.amu.edu.pl/s452639/psi
This commit is contained in:
commit
a760db4ef0
@ -1,4 +1,4 @@
|
|||||||
name icon category
|
name icon categories
|
||||||
airFrashener fa-wind home accessories
|
airFrashener fa-wind home accessories
|
||||||
apple fa-apple-alt food, fruits, fresh
|
apple fa-apple-alt food, fruits, fresh
|
||||||
babyAccessories fa-baby-carriage kids, accessories, hygiene
|
babyAccessories fa-baby-carriage kids, accessories, hygiene
|
||||||
|
|
@ -18,8 +18,11 @@
|
|||||||
<script src="configuration.js"></script>
|
<script src="configuration.js"></script>
|
||||||
<script src="utilities.js"></script>
|
<script src="utilities.js"></script>
|
||||||
<script src="logic/product.js"></script>
|
<script src="logic/product.js"></script>
|
||||||
|
<script src="logic/knowledge.js"></script>
|
||||||
<script src="logic/time.js"></script>
|
<script src="logic/time.js"></script>
|
||||||
<script src="logic/shelf.js"></script>
|
<script src="logic/shelf.js"></script>
|
||||||
|
<script src="logic/arrangment.js"></script>
|
||||||
|
<script src="logic/evolution.js"></script>
|
||||||
|
|
||||||
<script src="view/floor.js"></script>
|
<script src="view/floor.js"></script>
|
||||||
<script src="view/grid.js"></script>
|
<script src="view/grid.js"></script>
|
||||||
@ -28,9 +31,9 @@
|
|||||||
<script src="view/agentController.js"></script>
|
<script src="view/agentController.js"></script>
|
||||||
<script src="logic/agent.js"></script>
|
<script src="logic/agent.js"></script>
|
||||||
<script src="view/products.js"></script>
|
<script src="view/products.js"></script>
|
||||||
<script src="logic/knowledge.js"></script>
|
|
||||||
<script src="logic/shop.js"></script>
|
<script src="logic/shop.js"></script>
|
||||||
<script src="logic/costMap.js"></script>
|
<script src="logic/costMap.js"></script>
|
||||||
|
<script src="view/visualisation.js"></script>
|
||||||
|
|
||||||
<!-- Main script file -->
|
<!-- Main script file -->
|
||||||
<script src="main.js"></script>
|
<script src="main.js"></script>
|
||||||
@ -77,7 +80,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="logs-label">
|
<div class="logs-label">
|
||||||
<span>LOGS</span>
|
<span>INFO</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
@ -279,7 +282,20 @@
|
|||||||
|
|
||||||
<div class="logs-wrapper">
|
<div class="logs-wrapper">
|
||||||
<div class="logs-content">
|
<div class="logs-content">
|
||||||
<!-- All logs should go here -->
|
<div>
|
||||||
|
Nr <b id="populationIndex"></b> of <b id="populationSize"></b>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Similarity:
|
||||||
|
<b id="similarity"></b>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Best: <b id="best"></b>
|
||||||
|
</div>
|
||||||
|
<div>Worst: <b id="worst"></b></div>
|
||||||
|
<ul id="history">
|
||||||
|
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -10,8 +10,7 @@ class Agent extends AgentController {
|
|||||||
const cycle = async () => {
|
const cycle = async () => {
|
||||||
const jobRequest = Products.instance.getJobRequest();
|
const jobRequest = Products.instance.getJobRequest();
|
||||||
if (jobRequest) {
|
if (jobRequest) {
|
||||||
const requiredAmount = 50 - jobRequest.amount;
|
await this.takeFromStore(jobRequest.product, jobRequest.amount);
|
||||||
await this.takeFromStore(jobRequest.product, requiredAmount);
|
|
||||||
await this.deliver(jobRequest.x, jobRequest.y);
|
await this.deliver(jobRequest.x, jobRequest.y);
|
||||||
} else {
|
} else {
|
||||||
await waitFor(1000);
|
await waitFor(1000);
|
||||||
@ -44,7 +43,7 @@ class Agent extends AgentController {
|
|||||||
Products.instance.exchange(x, y, this.ownedProduct, 50);
|
Products.instance.exchange(x, y, this.ownedProduct, 50);
|
||||||
|
|
||||||
this.ownedProductAmount = 0;
|
this.ownedProductAmount = 0;
|
||||||
this.ownedProduct = Product.REGISTRY.empty;
|
this.ownedProduct = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
116
src/logic/arrangment.js
Normal file
116
src/logic/arrangment.js
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
const ShelfCount = 17 * 6;
|
||||||
|
const InRow = 3 * 6;
|
||||||
|
|
||||||
|
// index to abstract coordinates
|
||||||
|
function i2ac(index) {
|
||||||
|
const y = Math.floor(index / InRow);
|
||||||
|
const x = (index % InRow);
|
||||||
|
if (y === 5 && x >= 6)
|
||||||
|
return [x + 6, y];
|
||||||
|
return [x, y];
|
||||||
|
}
|
||||||
|
|
||||||
|
function ac2i(x, y) {
|
||||||
|
return y * InRow + x - (y === 5 && x >= 6) * 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
// index to grid coordinates
|
||||||
|
function i2gc(index) {
|
||||||
|
const [x, y] = i2ac(index);
|
||||||
|
return [x + Number(x > 11) + Number(x > 5), y + Math.floor((y + 1) / 2)];
|
||||||
|
}
|
||||||
|
|
||||||
|
function gc2i(x, y) {
|
||||||
|
x -= Number(x >= 13) + Number(x >= 6);
|
||||||
|
switch (y) {
|
||||||
|
case 0: y = 0; break;
|
||||||
|
case 2: y = 1; break;
|
||||||
|
case 3: y = 2; break;
|
||||||
|
case 5: y = 3; break;
|
||||||
|
case 6: y = 4; break;
|
||||||
|
case 8: y = 5; break;
|
||||||
|
}
|
||||||
|
return ac2i(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Arrangement {
|
||||||
|
generateRandom() {
|
||||||
|
this.products = [...Array(ShelfCount).keys()].map(() => Knowledge.semanticNetwork.getRandom().name);
|
||||||
|
this.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(products = []) {
|
||||||
|
this.products = products;
|
||||||
|
this.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this.productsAmount = null;
|
||||||
|
this.productsAttributes = null;
|
||||||
|
this.similarities = null;
|
||||||
|
this.cachedValidity = null;
|
||||||
|
this.similarity = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
clone() {
|
||||||
|
return JSON.parse(JSON.stringify(this.products));
|
||||||
|
}
|
||||||
|
|
||||||
|
simulate() {
|
||||||
|
this.similarities ||= [...Array(ShelfCount)].map(() => 0);
|
||||||
|
this.productsAmount ||= [...Array(ShelfCount)].map(() => 50);
|
||||||
|
this.productsAttributes ||= this.products.map(name => Knowledge.productAttributes.getRandomFor(name));
|
||||||
|
|
||||||
|
this.similarity = 0;
|
||||||
|
this.products.forEach((product, i) => {
|
||||||
|
let similarityWithNeighbours = 0;
|
||||||
|
const neighbours = neighboursByIndex(i);
|
||||||
|
for (const neighbour of neighbours) {
|
||||||
|
similarityWithNeighbours += similarity(product, this.products[neighbour]);
|
||||||
|
}
|
||||||
|
similarityWithNeighbours /= neighbours.length;
|
||||||
|
this.similarities[i] = 1 - similarityWithNeighbours;
|
||||||
|
this.similarity += this.similarities[i];
|
||||||
|
});
|
||||||
|
|
||||||
|
this.similarity /= ShelfCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
async validate() {
|
||||||
|
if (this.cachedValidity)
|
||||||
|
return this.cachedValidity;
|
||||||
|
|
||||||
|
const matchingShelfs = this.products.map(async (product, i) => {
|
||||||
|
if (!this.productsAttributes[i])
|
||||||
|
return;
|
||||||
|
|
||||||
|
let shelfType = await requestJSONCached('/api/decide', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
redirect: 'follow',
|
||||||
|
body: JSON.stringify(this.productsAttributes[i])
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!shelfType)
|
||||||
|
return;
|
||||||
|
shelfType = shelfType[0];
|
||||||
|
return Grid.instance.getShelf(...i2gc(i)).type === shelfType;
|
||||||
|
});
|
||||||
|
|
||||||
|
let m = 0;
|
||||||
|
for (const match of matchingShelfs)
|
||||||
|
switch (await match) {
|
||||||
|
case undefined:
|
||||||
|
case true:
|
||||||
|
m++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.cachedValidity = m / this.productsAttributes.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fitness() {
|
||||||
|
if (this.similarity === null) this.simulate();
|
||||||
|
if (this.cachedValidity === null) await this.validate();
|
||||||
|
return (this.cachedValidity + this.similarity) / 2;
|
||||||
|
}
|
||||||
|
}
|
@ -36,43 +36,3 @@ class CostMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CostMapVisualisation {
|
|
||||||
static instance
|
|
||||||
|
|
||||||
constructor(canvas) {
|
|
||||||
this.canvas = canvas
|
|
||||||
this.ctx = canvas.getContext('2d');
|
|
||||||
canvas.width = 2000
|
|
||||||
canvas.height = 900
|
|
||||||
|
|
||||||
CostMapVisualisation.instance = this
|
|
||||||
|
|
||||||
document.addEventListener('keydown', ({ key }) => {
|
|
||||||
if (key !== 'c')
|
|
||||||
return
|
|
||||||
|
|
||||||
if (this.hasBeenUpdated) {
|
|
||||||
this.ctx.clearRect(0, 0, 2000, 900);
|
|
||||||
} else {
|
|
||||||
this.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.hasBeenUpdated = !this.hasBeenUpdated
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
update() {
|
|
||||||
CostMap.instance.values.forEach((cost, i) => {
|
|
||||||
const y = Math.floor(i / 20) * 100;
|
|
||||||
const x = (i % 20) * 100;
|
|
||||||
|
|
||||||
let color = Math.floor((cost * 255)).toString(16)
|
|
||||||
if (color.length == 1)
|
|
||||||
color = "0" + color
|
|
||||||
|
|
||||||
this.ctx.fillStyle = '#' + color + color + color;
|
|
||||||
this.ctx.fillRect(x, y, 100, 100)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
180
src/logic/evolution.js
Normal file
180
src/logic/evolution.js
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
const PopulationSize = 31 * 2;
|
||||||
|
const PopulationToKeep = 0.3;
|
||||||
|
const MutationRate = 0.2;
|
||||||
|
|
||||||
|
class Evolution extends Arrangement {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.generate();
|
||||||
|
this.setup();
|
||||||
|
this.productIndex = 0;
|
||||||
|
this.productsAmount = [...Array(ShelfCount)].map(() => 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
generate() {
|
||||||
|
this.population = [...Array(PopulationSize)].map(() => {
|
||||||
|
const arr = new Arrangement();
|
||||||
|
arr.generateRandom();
|
||||||
|
return arr;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
this.populationIndex = 0;
|
||||||
|
this.population.forEach(x => x.simulate());
|
||||||
|
this.min_similarity = this.population.reduce((p, c) => Math.min(p, c.similarity || 1), 1);
|
||||||
|
this.max_similarity = this.population.reduce((p, c) => Math.max(p, c.similarity || 0), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
async next(days) {
|
||||||
|
for (let i = 0; i < Number(days) - 1; ++i) {
|
||||||
|
await this.step(false);
|
||||||
|
}
|
||||||
|
await this.step(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async step(render = true) {
|
||||||
|
// Complete previous day
|
||||||
|
this.products = this.population[this.populationIndex].products;
|
||||||
|
this.simulate();
|
||||||
|
this.productsAmount = [...Array(ShelfCount)].map((_, i) => Math.round(50 * this.similarities[i]));
|
||||||
|
const productsSold = ShelfCount * 50 - this.productsAmount.reduce((p, c) => p + c, 0);
|
||||||
|
|
||||||
|
// Setup new day
|
||||||
|
this.populationIndex++;
|
||||||
|
this.productIndex = 0;
|
||||||
|
|
||||||
|
// Generate new population if needed
|
||||||
|
if (this.populationIndex >= PopulationSize) {
|
||||||
|
/**
|
||||||
|
* @type{Arrangement[]}
|
||||||
|
*/
|
||||||
|
let newPopulation = [];
|
||||||
|
this.population.sort((a, b) => a.similarity - b.similarity);
|
||||||
|
|
||||||
|
let fitness = await Promise.all(this.population.map(arr => arr.fitness()));
|
||||||
|
let sum = fitness.reduce((p, c) => p + c, 0);
|
||||||
|
fitness = fitness.map(f => Math.pow(f / sum, 2));
|
||||||
|
|
||||||
|
const D = PopulationToKeep * PopulationSize;
|
||||||
|
|
||||||
|
for (let i = 0; i < this.population.length; ++i) {
|
||||||
|
const candidateA = this.population[pick(fitness)];
|
||||||
|
const candidateB = this.population[pick(fitness)];
|
||||||
|
const candidate = crossover(candidateA, candidateB);
|
||||||
|
mutate(candidate);
|
||||||
|
newPopulation.push(candidate);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.population = this.population.slice(0, D).concat(newPopulation).slice(0, PopulationSize);
|
||||||
|
this.setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (render) {
|
||||||
|
this.update();
|
||||||
|
console.log(productsSold)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getJobRequest() {
|
||||||
|
if (this.productIndex > ShelfCount)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
for (; this.productIndex < ShelfCount; ++this.productIndex) {
|
||||||
|
const inPopulation = this.population[this.populationIndex];
|
||||||
|
let amount = 50;
|
||||||
|
if (this.products[this.productIndex] === inPopulation.products[this.productIndex]) {
|
||||||
|
if (this.productsAmount[this.productIndex] == 50)
|
||||||
|
continue;
|
||||||
|
amount -= this.productsAmount[this.productIndex];
|
||||||
|
}
|
||||||
|
const [x, y] = i2gc(this.productIndex++);
|
||||||
|
return {
|
||||||
|
product: Knowledge.semanticNetwork.findProductByName(this.population[this.populationIndex].products[this.productIndex]),
|
||||||
|
amount,
|
||||||
|
x, y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getSimilarity() {
|
||||||
|
try {
|
||||||
|
this.population[this.populationIndex].simulate();
|
||||||
|
return this.population[this.populationIndex].similarity;
|
||||||
|
} catch (e) {
|
||||||
|
return this.similarity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Arrangement} arr
|
||||||
|
*/
|
||||||
|
function mutate(arr) {
|
||||||
|
const { floor, random } = Math;
|
||||||
|
for (let i = 0; i < ShelfCount / 6; ++i) {
|
||||||
|
while (random() <= MutationRate) {
|
||||||
|
const a = floor(random() * arr.products.length);
|
||||||
|
const b = floor(random() * arr.products.length);
|
||||||
|
|
||||||
|
if (a !== b) {
|
||||||
|
let t = arr.products[a];
|
||||||
|
arr.products[a] = arr.products[b];
|
||||||
|
arr.products[b] = t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param {Arrangement} ca
|
||||||
|
* @param {Arrangement} cb
|
||||||
|
*/
|
||||||
|
function crossover(ca, cb) {
|
||||||
|
const child = [];
|
||||||
|
for (let i = 0; i < ca.products.length; ++i)
|
||||||
|
child.push(ca.similarities[i] > cb.similarities[i] ? ca.products[i] : cb.products[i]);
|
||||||
|
return new Arrangement(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
function neighboursByIndex(index) {
|
||||||
|
const [bx, by] = i2ac(index);
|
||||||
|
const at = (dx, dy) => {
|
||||||
|
const x = bx + dx;
|
||||||
|
const y = by + dy;
|
||||||
|
if (by != y) {
|
||||||
|
if ((by === 1 || by === 3) && (by - y < 0)) return [];
|
||||||
|
if ((by === 2 || by === 4) && (by - y > 0)) return [];
|
||||||
|
}
|
||||||
|
if (x >= 0 && y >= 0) {
|
||||||
|
const index = ac2i(x, y);
|
||||||
|
if (index < ShelfCount)
|
||||||
|
return [index];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
|
||||||
|
return [at(0, -1), at(-1, 0), at(1, 0), at(0, 1)].flat();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// given two products, find how similar they are based on categories
|
||||||
|
// similiary is defined as euclidian distance
|
||||||
|
function similarity(leftProductName, rightProductName) {
|
||||||
|
const lhs = Knowledge.semanticNetwork.findByName(leftProductName);
|
||||||
|
const rhs = Knowledge.semanticNetwork.findByName(rightProductName);
|
||||||
|
console.assert(lhs && rhs);
|
||||||
|
|
||||||
|
const categories = new Set([...lhs.categories, ...rhs.categories]);
|
||||||
|
const d = 1 / categories.size;
|
||||||
|
|
||||||
|
let similarity = 0;
|
||||||
|
for (const category of categories) {
|
||||||
|
similarity += Math.pow(
|
||||||
|
(lhs.categories.indexOf(category) >= 0 ? d : 0) -
|
||||||
|
(rhs.categories.indexOf(category) >= 0 ? d : 0), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.sqrt(similarity);
|
||||||
|
}
|
@ -6,31 +6,20 @@ function flattenToUnique(array, view) {
|
|||||||
}, new Set())
|
}, new Set())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function intoProduct({ name, icon }) {
|
||||||
|
return new Product(name, icon)
|
||||||
|
}
|
||||||
|
|
||||||
class SemanticNetwork {
|
class SemanticNetwork {
|
||||||
/**
|
/**
|
||||||
* @param {string} definition
|
* @param {{
|
||||||
|
* name: string;
|
||||||
|
* icon: string;
|
||||||
|
* categories: string[];
|
||||||
|
* }[]} data
|
||||||
*/
|
*/
|
||||||
constructor(definition) {
|
constructor(data) {
|
||||||
/**
|
this.data = data;
|
||||||
* @param {string} stmt
|
|
||||||
* @returns {[{ name: string; categories: string[]; locations: string[] }]}
|
|
||||||
*/
|
|
||||||
function parseStatement(stmt) {
|
|
||||||
const [name, ...keywords] = stmt.split(':').map(x => x.trim())
|
|
||||||
const o = { name }
|
|
||||||
for (const keyword of keywords) {
|
|
||||||
const command = keyword[0]
|
|
||||||
const items = keyword.slice(1).split(',').map(x => x.trim()).filter(x => x.length > 0)
|
|
||||||
switch (command) {
|
|
||||||
case 'c': o.categories = items; break
|
|
||||||
case 'p': o.locations = items; break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
|
|
||||||
this.data = definition.split('\n').filter(x => x.trim().length > 0)
|
|
||||||
.map(parseStatement)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getAllNames() {
|
getAllNames() {
|
||||||
@ -41,10 +30,6 @@ class SemanticNetwork {
|
|||||||
return flattenToUnique(this.data, ({ categories }) => categories)
|
return flattenToUnique(this.data, ({ categories }) => categories)
|
||||||
}
|
}
|
||||||
|
|
||||||
getAllLocations() {
|
|
||||||
return flattenToUnique(this.data, ({ locations }) => locations)
|
|
||||||
}
|
|
||||||
|
|
||||||
findByName(nameToFind) {
|
findByName(nameToFind) {
|
||||||
return this.data.find(({ name }) => name === nameToFind)
|
return this.data.find(({ name }) => name === nameToFind)
|
||||||
}
|
}
|
||||||
@ -53,12 +38,44 @@ class SemanticNetwork {
|
|||||||
return this.data.filter(({ categories }) => categories.indexOf(categoryToFind) >= 0)
|
return this.data.filter(({ categories }) => categories.indexOf(categoryToFind) >= 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
findAllByLocation(locationToFind) {
|
getRandom() {
|
||||||
return this.data.filter(({ locations }) => locations.indexOf(locationToFind) >= 0)
|
const { floor, random } = Math;
|
||||||
|
return this.data[floor(random() * this.data.length)];
|
||||||
|
}
|
||||||
|
|
||||||
|
findProductByName(nameToFind) {
|
||||||
|
return intoProduct(this.findByName(nameToFind));
|
||||||
|
}
|
||||||
|
|
||||||
|
getRandomProduct() {
|
||||||
|
return intoProduct(this.getRandom());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AgentSemanticNetwork extends SemanticNetwork {}
|
class AttributesSemanticNetwork {
|
||||||
|
constructor(data) {
|
||||||
|
this.entries = {}
|
||||||
|
for (const { nazwa, polka, ...attr } of data) {
|
||||||
|
if (!this.entries[nazwa]) {
|
||||||
|
this.entries[nazwa] = Object.entries(attr).reduce((p, [key, value]) => ({ ...p, [key]: new Set([value]) }), {});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(attr)) {
|
||||||
|
this.entries[nazwa][key].add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getRandomFor(productName) {
|
||||||
|
const attributes = this.entries[productName];
|
||||||
|
return attributes && Object.entries(attributes)
|
||||||
|
.reduce((p, [key, values]) => {
|
||||||
|
const r = randomFromSet(values);
|
||||||
|
return r !== 'nie_dotyczy' ? { ...p, [key]: r } : p;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function nearbyStorageUnitsCoords(gridX, gridY) {
|
function nearbyStorageUnitsCoords(gridX, gridY) {
|
||||||
function outsideOfStorageCenter(v) {
|
function outsideOfStorageCenter(v) {
|
||||||
@ -80,51 +97,37 @@ function nearbyStorageUnitsIndexes(gridX, gridY) {
|
|||||||
.map(({ x, y }) => x * UnitsCount + y * UnitsCount * RowsOfGroupsCount)
|
.map(({ x, y }) => x * UnitsCount + y * UnitsCount * RowsOfGroupsCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
class Arrangement {
|
|
||||||
constructor() {
|
|
||||||
this.products = [...Array(UnitsCount)].map(() => ({
|
|
||||||
product: '',
|
|
||||||
count: 0,
|
|
||||||
icon: ''
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
nearbyProducts(gridX, gridY) {
|
async function loadTsvFile(path, nested = []) {
|
||||||
return nearbyStorageUnitsIndexes(gridX, gridY)
|
const response = await fetch(path);
|
||||||
.map(i => this.products[i])
|
const text = await response.text();
|
||||||
}
|
|
||||||
|
const [header, ...rows] = text.split('\n')
|
||||||
|
.map(x => x.trim())
|
||||||
|
.filter(x => x)
|
||||||
|
.map(x => x.split('\t')
|
||||||
|
.map(cell => cell.split(',').map(x => x.trim())));
|
||||||
|
|
||||||
|
return rows.map(row =>
|
||||||
|
row.reduce((p, c, i) => ({
|
||||||
|
...p,
|
||||||
|
[header[i]]: nested.includes(i) ? c : c[0] }), {}));
|
||||||
}
|
}
|
||||||
|
|
||||||
class Knowledge {
|
class Knowledge {
|
||||||
static semanticNetwork
|
|
||||||
static agentSemanticNetwork
|
|
||||||
static arrangement
|
|
||||||
|
|
||||||
constructor(definition) {
|
/**
|
||||||
Knowledge.semanticNetwork = new SemanticNetwork(definition)
|
* @type{SemanticNetwork}
|
||||||
Knowledge.agentSemanticNetwork = new AgentSemanticNetwork(definition)
|
*/
|
||||||
Knowledge.arrangement = new Arrangement()
|
static semanticNetwork
|
||||||
|
|
||||||
|
static async load() {
|
||||||
|
const [ products, attributes ] = await Promise.all([
|
||||||
|
loadTsvFile('/data/products.tsv', [ 2 ]),
|
||||||
|
loadTsvFile('/data/productsTree.tsv')
|
||||||
|
]);
|
||||||
|
|
||||||
|
Knowledge.semanticNetwork = new SemanticNetwork(products);
|
||||||
|
Knowledge.productAttributes = new AttributesSemanticNetwork(attributes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
keywords:
|
|
||||||
p - powiązane pomieszczenia / miejsca
|
|
||||||
c - category
|
|
||||||
*/
|
|
||||||
new Knowledge(`
|
|
||||||
Piłka :c sport,rozrywka, piłka nożna
|
|
||||||
Kubek klubowy :c sport,rozrywka,piłka nożna :p kuchnia
|
|
||||||
Waga łazienkowa :c zdrowie,agd,elektronika :p łazienka
|
|
||||||
Czajnik elektryczny :c agd,elektronika,zdrowie :p kuchnia
|
|
||||||
Widelec :c agd,jedzenie,zastawa kuchenna :p kuchnia
|
|
||||||
Łyżka :c agd,jedzenie,zastawa kuchenna :p kuchnia
|
|
||||||
Nóż stołowy :c agd,jedzenie,zastawa kuchenna :p kuchnia
|
|
||||||
Nóż kuchenny :c agd,gotowanie,przyrząd kuchenny :p kuchnia
|
|
||||||
Deska do krojenia :cagd,gotowanie,przyrząd kuchenny :p kuchnia
|
|
||||||
Gąbki kuchenne :c agd,gotowanie,sprzątanie :p kuchnia
|
|
||||||
Worki na śmieci :c agd,gotowanie,sprzątanie :p kuchnia
|
|
||||||
Miska :c agd,jedzenie,gotowanie,zastawa kuchenna :p kuchnia
|
|
||||||
Laptop :c elektronika,gaming :p biuro,szkoła
|
|
||||||
Telefon :c elektronika :p biuro,szkoła
|
|
||||||
`)
|
|
||||||
|
@ -23,6 +23,7 @@ class Product {
|
|||||||
equals(other) {
|
equals(other) {
|
||||||
return this.name === other.name;
|
return this.name === other.name;
|
||||||
}
|
}
|
||||||
|
<<<<<<< HEAD
|
||||||
|
|
||||||
static get REGISTRY() {
|
static get REGISTRY() {
|
||||||
// in alphabetical order
|
// in alphabetical order
|
||||||
@ -153,4 +154,6 @@ class Product {
|
|||||||
static RANDOM_FROM_REGISTRY() {
|
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))]];
|
return Product.REGISTRY[Object.keys(Product.REGISTRY).filter(p => p !== 'empty')[Math.floor(Math.random() * (Object.keys(Product.REGISTRY).length - 1))]];
|
||||||
}
|
}
|
||||||
|
=======
|
||||||
|
>>>>>>> 4bd5bbdc0a26639410a6634debaff60c8a484593
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
class Time {
|
class Time {
|
||||||
|
|
||||||
isPaused = false;
|
isPaused = true;
|
||||||
timeMultiplier = 1;
|
timeMultiplier = 1;
|
||||||
day = 0;
|
day = 0;
|
||||||
|
|
||||||
@ -57,8 +57,14 @@ class Time {
|
|||||||
/**
|
/**
|
||||||
* Add days
|
* Add days
|
||||||
*/
|
*/
|
||||||
addDays(days) {
|
async addDays(days) {
|
||||||
|
await Products.instance.next(days);
|
||||||
|
Products.instance.update();
|
||||||
|
const ul = document.querySelector('ul#history');
|
||||||
this.setDay(this.day += Number(days));
|
this.setDay(this.day += Number(days));
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.innerHTML = `${this.day}: ${nice(Products.instance.max_similarity)}`;
|
||||||
|
ul.appendChild(li);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,4 +1,6 @@
|
|||||||
window.addEventListener('DOMContentLoaded', () => document.fonts.load('900 14px "Font Awesome 5 Free"').then(() => {
|
window.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
await document.fonts.load('900 14px "Font Awesome 5 Free"')
|
||||||
|
|
||||||
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,10 +10,11 @@ window.addEventListener('DOMContentLoaded', () => document.fonts.load('900 14px
|
|||||||
const costMap = new CostMap();
|
const costMap = new CostMap();
|
||||||
const time = new Time();
|
const time = new Time();
|
||||||
const timeController = new TimeController();
|
const timeController = new TimeController();
|
||||||
const costMapVisualisation = new CostMapVisualisation(costCanvas);
|
|
||||||
const floor = new Floor(floorCanvas);
|
const floor = new Floor(floorCanvas);
|
||||||
const grid = new Grid(gridCanvas);
|
const grid = new Grid(gridCanvas);
|
||||||
const agent = new Agent(agentCanvas);
|
const agent = new Agent(agentCanvas);
|
||||||
|
|
||||||
|
await Knowledge.load()
|
||||||
const products = new Products(productsCanvas);
|
const products = new Products(productsCanvas);
|
||||||
|
|
||||||
const fpsElement = document.getElementById('fps');
|
const fpsElement = document.getElementById('fps');
|
||||||
@ -20,7 +23,7 @@ window.addEventListener('DOMContentLoaded', () => document.fonts.load('900 14px
|
|||||||
} else {
|
} else {
|
||||||
document.body.removeChild(fpsElement);
|
document.body.removeChild(fpsElement);
|
||||||
}
|
}
|
||||||
}));
|
});
|
||||||
|
|
||||||
function displayFPS(fpsElement) {
|
function displayFPS(fpsElement) {
|
||||||
let old = new Date();
|
let old = new Date();
|
||||||
|
@ -107,6 +107,8 @@ main {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
padding: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logs-content::-webkit-scrollbar {
|
.logs-content::-webkit-scrollbar {
|
||||||
|
@ -41,3 +41,41 @@ function Enum(...labels) {
|
|||||||
}
|
}
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function pick(array) {
|
||||||
|
let index = 0;
|
||||||
|
for (let r = Math.random(); r >= 0; r -= array[index]) {
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
return index - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function unreachable() {
|
||||||
|
throw new Error('unreachable');
|
||||||
|
}
|
||||||
|
|
||||||
|
function randomFromSet(set) {
|
||||||
|
const r = Math.floor(Math.random() * set.size);
|
||||||
|
let i = 0;
|
||||||
|
for (const value of set.values()) {
|
||||||
|
if (i++ === r)
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
unreachable();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function nice(v) { return `${(v * 100).toFixed(1)}%` }
|
||||||
|
|
||||||
|
async function requestJSONCached(url, params = {}) {
|
||||||
|
const key = url + '\n' + (params?.body || '');
|
||||||
|
if (requestJSONCached.cache.has(key))
|
||||||
|
return requestJSONCached.cache.get(key);
|
||||||
|
|
||||||
|
const response = await fetch(url, params);
|
||||||
|
const json = await response.json();
|
||||||
|
requestJSONCached.cache.set(key, json);
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
requestJSONCached.cache = new Map();
|
@ -4,7 +4,7 @@ class AgentController {
|
|||||||
this.ctx = canvas.getContext('2d');
|
this.ctx = canvas.getContext('2d');
|
||||||
this.setCanvasSize(canvas);
|
this.setCanvasSize(canvas);
|
||||||
|
|
||||||
this.ownedProduct = Product.REGISTRY.empty;
|
this.ownedProduct = null;
|
||||||
this.ownedProductAmount = 0;
|
this.ownedProductAmount = 0;
|
||||||
|
|
||||||
// Akcje
|
// Akcje
|
||||||
@ -244,17 +244,19 @@ class AgentController {
|
|||||||
this.ctx.rotate(-rotation * Math.PI / 180);
|
this.ctx.rotate(-rotation * Math.PI / 180);
|
||||||
|
|
||||||
// Product
|
// Product
|
||||||
this.ctx.font = '900 40px "Font Awesome 5 Free"';
|
if (this.ownedProduct) {
|
||||||
this.ctx.textAlign = 'center';
|
this.ctx.font = '900 40px "Font Awesome 5 Free"';
|
||||||
this.ctx.fillStyle = 'white';
|
this.ctx.textAlign = 'center';
|
||||||
this.ctx.fillText(this.ownedProduct.icon, 0, 15);
|
this.ctx.fillStyle = 'white';
|
||||||
|
this.ctx.fillText(this.ownedProduct.icon, 0, 15);
|
||||||
|
|
||||||
// Amount
|
// Amount
|
||||||
if (this.ownedProductAmount) {
|
if (this.ownedProductAmount) {
|
||||||
this.ctx.font = '20px Montserrat';
|
this.ctx.font = '20px Montserrat';
|
||||||
this.ctx.textAlign = 'left';
|
this.ctx.textAlign = 'left';
|
||||||
this.ctx.fillStyle = 'white';
|
this.ctx.fillStyle = 'white';
|
||||||
this.ctx.fillText(this.ownedProductAmount, 10, 35);
|
this.ctx.fillText(this.ownedProductAmount, 10, 35);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ctx.translate(-(newX + (w / 2)), -(newY + (h / 2)));
|
this.ctx.translate(-(newX + (w / 2)), -(newY + (h / 2)));
|
||||||
|
@ -5,6 +5,9 @@ const GRID_FIELD_TYPE = Object.freeze({
|
|||||||
});
|
});
|
||||||
|
|
||||||
class Grid {
|
class Grid {
|
||||||
|
/**
|
||||||
|
* @type{Grid}
|
||||||
|
*/
|
||||||
static instance;
|
static instance;
|
||||||
|
|
||||||
constructor(canvas) {
|
constructor(canvas) {
|
||||||
@ -31,6 +34,10 @@ class Grid {
|
|||||||
return this.shelves;
|
return this.shelves;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getShelf(x, y) {
|
||||||
|
return this.shelves[x][y];
|
||||||
|
}
|
||||||
|
|
||||||
setCanvasSize (canvas) {
|
setCanvasSize (canvas) {
|
||||||
canvas.width = 2000;
|
canvas.width = 2000;
|
||||||
canvas.height = 900;
|
canvas.height = 900;
|
||||||
|
@ -9,38 +9,20 @@ function filterReduce2D(array, reducer, filter, init) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
class Products {
|
class Products extends Evolution {
|
||||||
|
/**
|
||||||
|
* @type{Products}
|
||||||
|
*/
|
||||||
static instance
|
static instance
|
||||||
|
|
||||||
constructor(canvas) {
|
constructor(canvas) {
|
||||||
const { random, floor } = Math
|
super();
|
||||||
|
// this.generateRandom();
|
||||||
this.gridProducts = []
|
|
||||||
this.gridProductsAmount = []
|
|
||||||
this.min = 50;
|
|
||||||
|
|
||||||
for (let i = 0; i < 20; ++i) {
|
|
||||||
const gridProductsColumn = []
|
|
||||||
const gridProductsAmountColumn = []
|
|
||||||
|
|
||||||
for (let j = 0; j < 9; ++j) {
|
|
||||||
if (j % 3 !== 1 && (i+1) % 7 !== 0) {
|
|
||||||
gridProductsColumn.push(Product.RANDOM_FROM_REGISTRY());
|
|
||||||
const amount = floor(random() * 51);
|
|
||||||
gridProductsAmountColumn.push(amount);
|
|
||||||
this.min = this.min > amount ? amount : this.min;
|
|
||||||
} else {
|
|
||||||
gridProductsColumn.push('');
|
|
||||||
gridProductsAmountColumn.push('');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.gridProducts.push(gridProductsColumn);
|
|
||||||
this.gridProductsAmount.push(gridProductsAmountColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ctx = canvas.getContext('2d');
|
this.ctx = canvas.getContext('2d');
|
||||||
this.setCanvasSize(canvas);
|
this.setCanvasSize(canvas);
|
||||||
|
|
||||||
this.update();
|
this.update();
|
||||||
|
|
||||||
Products.instance = this
|
Products.instance = this
|
||||||
@ -51,33 +33,47 @@ class Products {
|
|||||||
canvas.height = 900;
|
canvas.height = 900;
|
||||||
}
|
}
|
||||||
|
|
||||||
update(){
|
update() {
|
||||||
|
function write(tag, text) {
|
||||||
|
const qr = document.querySelector(tag);
|
||||||
|
if (qr) {
|
||||||
|
qr.innerHTML = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
write('#similarity', nice(this.getSimilarity()));
|
||||||
|
write('#populationIndex', this.populationIndex + 1);
|
||||||
|
write('#populationSize', PopulationSize);
|
||||||
|
write('#best', nice(this.max_similarity));
|
||||||
|
write('#worst', nice(this.min_similarity));
|
||||||
|
|
||||||
this.clearCanvas();
|
this.clearCanvas();
|
||||||
this.drawProducts();
|
this.drawProducts();
|
||||||
}
|
}
|
||||||
|
|
||||||
drawProducts () {
|
drawProducts() {
|
||||||
for (let [x, line] of Grid.instance.grid.entries()) {
|
this.min = this.productsAmount?.reduce((p, c) => Math.min(p, c), 50) || 50;
|
||||||
for (let [y, type] of line.entries()) {
|
this.products.forEach((product, index) => {
|
||||||
if (type === GRID_FIELD_TYPE.SHELF) {
|
let [x, y] = i2gc(index);
|
||||||
let v = this.drawValue(x, y);
|
x *= 100; y *= 100;
|
||||||
this.product(x * 100, y * 100, v, x, y);
|
const amount = this.productsAmount === null ? 50 : this.productsAmount[index];
|
||||||
}
|
this.drawAmount(x, y, amount);
|
||||||
}
|
const { icon } = Knowledge.semanticNetwork.findProductByName(product);
|
||||||
}
|
this.product(x, y, icon, amount);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
drawValue(x, y) {
|
drawAmount(x, y, amount) {
|
||||||
let fontSize = 20;
|
let fontSize = 20;
|
||||||
this.ctx.font = `${fontSize}px Montserrat`;
|
this.ctx.font = `${fontSize}px Montserrat`;
|
||||||
this.ctx.textAlign = "left";
|
this.ctx.textAlign = "left";
|
||||||
this.ctx.fillStyle = 'white';
|
this.ctx.fillStyle = 'white';
|
||||||
let number = this.gridProductsAmount[x][y];
|
this.ctx.fillText(amount, x + 10, y + 90);
|
||||||
this.ctx.fillText(number, (x * 100) + 10, (y * 100) + 90);
|
|
||||||
return number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
product(x, y, v, productsKey, productsValue) {
|
product(x, y, icon, amount) {
|
||||||
let fontSize = 40;
|
let fontSize = 40;
|
||||||
this.ctx.font = `900 ${fontSize}px "Font Awesome 5 Free"`;
|
this.ctx.font = `900 ${fontSize}px "Font Awesome 5 Free"`;
|
||||||
this.ctx.textAlign = "center";
|
this.ctx.textAlign = "center";
|
||||||
@ -86,17 +82,15 @@ class Products {
|
|||||||
// white - full shelf
|
// white - full shelf
|
||||||
// red - empty shelf
|
// red - empty shelf
|
||||||
const t = Math.cbrt
|
const t = Math.cbrt
|
||||||
v = (v - this.min) / (50 - this.min);
|
const v = (amount - this.min) / (50 - this.min);
|
||||||
let color = Math.floor(t(v) * 255).toString(16)
|
let color = Math.floor(t(v) * 255).toString(16)
|
||||||
if (color.length == 1)
|
if (color.length == 1)
|
||||||
color = "0" + color
|
color = "0" + color
|
||||||
this.ctx.fillStyle = `#ff${color}${color}`;
|
this.ctx.fillStyle = `#ff${color}${color}`;
|
||||||
|
|
||||||
let productsColumn = this.gridProducts[productsKey];
|
|
||||||
let prdct = productsColumn[productsValue];
|
|
||||||
this.ctx.font = '900 40px "Font Awesome 5 Free"';
|
this.ctx.font = '900 40px "Font Awesome 5 Free"';
|
||||||
this.ctx.textAlign = 'center';
|
this.ctx.textAlign = 'center';
|
||||||
this.ctx.fillText(prdct.icon, x+50, y+60);
|
this.ctx.fillText(icon, x + 50, y + 60);
|
||||||
}
|
}
|
||||||
|
|
||||||
clearCanvas() {
|
clearCanvas() {
|
||||||
@ -142,13 +136,13 @@ class Products {
|
|||||||
* @returns {{product: Product; amount: number} | null } Old shelf content
|
* @returns {{product: Product; amount: number} | null } Old shelf content
|
||||||
*/
|
*/
|
||||||
exchange(x, y, incomingProduct, incomingAmount) {
|
exchange(x, y, incomingProduct, incomingAmount) {
|
||||||
const product = this.gridProducts[x][y];
|
const index = gc2i(x, y);
|
||||||
const amount = this.gridProductsAmount[x][y];
|
const product = this.products[index];
|
||||||
this.gridProducts[x][y] = incomingProduct;
|
const amount = this.productsAmount[index];
|
||||||
this.gridProductsAmount[x][y] = incomingAmount;
|
this.products[index] = incomingProduct.name;
|
||||||
|
this.productsAmount[index] = incomingAmount;
|
||||||
|
|
||||||
this.min = filterReduce2D(this.gridProductsAmount, (p, c) => Math.min(p, c),
|
this.min = this.productsAmount?.reduce((p, c) => Math.min(p, c), 50) || 50;
|
||||||
x => typeof x === 'number', 50);
|
|
||||||
|
|
||||||
this.clearCanvas();
|
this.clearCanvas();
|
||||||
this.drawProducts();
|
this.drawProducts();
|
||||||
@ -170,13 +164,6 @@ class Products {
|
|||||||
return this.exchange(x, y, incomingProduct, incomingAmount)
|
return this.exchange(x, y, incomingProduct, incomingAmount)
|
||||||
}
|
}
|
||||||
|
|
||||||
getJobRequest() {
|
|
||||||
const maybeProduct = this.filter((_product, amount, _i, _j, end) => amount < 50).sort((a, b) => {
|
|
||||||
return a.amount - b.amount
|
|
||||||
})
|
|
||||||
return maybeProduct.length === 0 ? null : maybeProduct[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
findAvailableLocationsFor(product) {
|
findAvailableLocationsFor(product) {
|
||||||
return this.filter((p, amount) => product.equals(p) && amount < 50)
|
return this.filter((p, amount) => product.equals(p) && amount < 50)
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ class TimeController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
readFromDayInput() {
|
async readFromDayInput() {
|
||||||
Time.instance.addDays(document.getElementById('dayInput').value);
|
await Time.instance.addDays(document.getElementById('dayInput').value);
|
||||||
}
|
}
|
||||||
}
|
}
|
53
src/view/visualisation.js
Normal file
53
src/view/visualisation.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
class Visualisation {
|
||||||
|
static instance
|
||||||
|
|
||||||
|
constructor(canvas) {
|
||||||
|
this.canvas = canvas
|
||||||
|
this.ctx = canvas.getContext('2d');
|
||||||
|
canvas.width = 2000
|
||||||
|
canvas.height = 900
|
||||||
|
|
||||||
|
Visualisation.instance = this
|
||||||
|
|
||||||
|
document.addEventListener('keydown', ({ key }) => {
|
||||||
|
const keys = {
|
||||||
|
c: () => this.drawCostMap(),
|
||||||
|
p: () => this.drawSimilarities(),
|
||||||
|
};
|
||||||
|
|
||||||
|
this.ctx.clearRect(0, 0, 2000, 900);
|
||||||
|
if (this.lastkey != key) {
|
||||||
|
for (const k in keys)
|
||||||
|
if (k == key) {
|
||||||
|
keys[k]();
|
||||||
|
this.lastkey = key;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
this.lastkey = null;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
drawSimilarities() {
|
||||||
|
Products.instance.similarities.map((similarity, index) => {
|
||||||
|
const [x, y] = i2gc(index);
|
||||||
|
|
||||||
|
let color = Math.floor(similarity * 120);
|
||||||
|
this.ctx.fillStyle = `hsl(${color}, 100%, 50%)`;
|
||||||
|
this.ctx.fillRect(x * 100, y * 100, 100, 100)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
drawCostMap() {
|
||||||
|
CostMap.instance.values.forEach((cost, i) => {
|
||||||
|
const y = Math.floor(i / 20) * 100;
|
||||||
|
const x = (i % 20) * 100;
|
||||||
|
|
||||||
|
let color = Math.floor((cost * 255)).toString(16)
|
||||||
|
if (color.length == 1)
|
||||||
|
color = "0" + color
|
||||||
|
|
||||||
|
this.ctx.fillStyle = '#' + color + color + color;
|
||||||
|
this.ctx.fillRect(x, y, 100, 100)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user