diff --git a/src/index.html b/src/index.html
index cd9f312..8def0ba 100644
--- a/src/index.html
+++ b/src/index.html
@@ -21,6 +21,7 @@
+
diff --git a/src/logic/arrangment.js b/src/logic/arrangment.js
new file mode 100644
index 0000000..3a91a8d
--- /dev/null
+++ b/src/logic/arrangment.js
@@ -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;
+ }
+}
diff --git a/src/logic/evolution.js b/src/logic/evolution.js
index 55f8fb3..c850a9d 100644
--- a/src/logic/evolution.js
+++ b/src/logic/evolution.js
@@ -1,75 +1,3 @@
-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.productsAmount = null;
- this.similarities = null;
- }
-
- constructor(products = []) {
- this.products = products;
- this.productsAmount = null;
- this.similarities = null;
- }
-
- clone() {
- return JSON.parse(JSON.stringify(this.products));
- }
-
- simulate() {
- this.similarities ||= [...Array(ShelfCount)].map(() => 0);
- this.productsAmount ||= [...Array(ShelfCount)].map(() => 50);
-
- 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;
- }
-}
-
const PopulationSize = 31 * 2;
const PopulationToKeep = 0.3;
const MutationRate = 0.2;
@@ -98,14 +26,15 @@ class Evolution extends Arrangement {
this.max_similarity = this.population.reduce((p, c) => Math.max(p, c.similarity || 0), 0);
}
- next(days) {
+ async next(days) {
for (let i = 0; i < Number(days) - 1; ++i) {
- this.step(false);
+ await this.step(false);
}
- this.step(true);
+ await this.step(true);
}
- step(render = true) {
+
+ async step(render = true) {
// Complete previous day
this.products = this.population[this.populationIndex].products;
this.simulate();
@@ -124,7 +53,7 @@ class Evolution extends Arrangement {
let newPopulation = [];
this.population.sort((a, b) => a.similarity - b.similarity);
- let fitness = this.population.map(({ similarity }) => 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));
diff --git a/src/logic/knowledge.js b/src/logic/knowledge.js
index b53eeae..18b6b91 100644
--- a/src/logic/knowledge.js
+++ b/src/logic/knowledge.js
@@ -52,6 +52,31 @@ class 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 outsideOfStorageCenter(v) {
const { x, y, w, h } = StorageCenterLocation
@@ -73,6 +98,22 @@ function nearbyStorageUnitsIndexes(gridX, gridY) {
}
+async function loadTsvFile(path, nested = []) {
+ const response = await fetch(path);
+ 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 {
/**
@@ -80,51 +121,13 @@ class Knowledge {
*/
static semanticNetwork
- static async loadFromFile(path) {
- const response = await fetch(path);
- 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())));
+ static async load() {
+ const [ products, attributes ] = await Promise.all([
+ loadTsvFile('/data/products.tsv', [ 2 ]),
+ loadTsvFile('/data/productsTree.tsv')
+ ]);
- const data = rows.map(row =>
- row.reduce((p, c, i) => ({
- ...p,
- [header[i]]: i == 2 ? c : c[0] }), {}));
-
- Knowledge.semanticNetwork = new SemanticNetwork(data);
- }
-
- constructor() {
- Knowledge.semanticNetwork = new SemanticNetwork(definition)
- Knowledge.arrangement = new Arrangement()
+ 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
-`)
-*/
\ No newline at end of file
diff --git a/src/logic/time.js b/src/logic/time.js
index 762b696..263baf2 100644
--- a/src/logic/time.js
+++ b/src/logic/time.js
@@ -57,8 +57,8 @@ class Time {
/**
* Add days
*/
- addDays(days) {
- Products.instance.next(days);
+ async addDays(days) {
+ await Products.instance.next(days);
Products.instance.update();
const ul = document.querySelector('ul#history');
this.setDay(this.day += Number(days));
diff --git a/src/main.js b/src/main.js
index 1326ab4..fa24462 100644
--- a/src/main.js
+++ b/src/main.js
@@ -14,7 +14,7 @@ window.addEventListener('DOMContentLoaded', async () => {
const grid = new Grid(gridCanvas);
const agent = new Agent(agentCanvas);
- await Knowledge.loadFromFile('/data/products.tsv')
+ await Knowledge.load()
const products = new Products(productsCanvas);
const fpsElement = document.getElementById('fps');
diff --git a/src/utilities.js b/src/utilities.js
index 614b4d2..9aa390e 100644
--- a/src/utilities.js
+++ b/src/utilities.js
@@ -50,4 +50,32 @@ function pick(array) {
return index - 1;
}
-function nice(v) { return `${(v * 100).toFixed(1)}%` }
\ No newline at end of file
+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();
\ No newline at end of file
diff --git a/src/view/grid.js b/src/view/grid.js
index 768fba7..f90b7cd 100644
--- a/src/view/grid.js
+++ b/src/view/grid.js
@@ -5,6 +5,9 @@ const GRID_FIELD_TYPE = Object.freeze({
});
class Grid {
+ /**
+ * @type{Grid}
+ */
static instance;
constructor(canvas) {
@@ -31,6 +34,10 @@ class Grid {
return this.shelves;
}
+ getShelf(x, y) {
+ return this.shelves[x][y];
+ }
+
setCanvasSize (canvas) {
canvas.width = 2000;
canvas.height = 900;
diff --git a/src/view/timeController.js b/src/view/timeController.js
index e9655bb..4c39cfd 100644
--- a/src/view/timeController.js
+++ b/src/view/timeController.js
@@ -13,7 +13,7 @@ class TimeController {
}
}
- readFromDayInput() {
- Time.instance.addDays(document.getElementById('dayInput').value);
+ async readFromDayInput() {
+ await Time.instance.addDays(document.getElementById('dayInput').value);
}
}
\ No newline at end of file