Skip to content

Commit

Permalink
Unnoticeable changes and improved commenting
Browse files Browse the repository at this point in the history
  • Loading branch information
psfer07 committed Oct 11, 2024
1 parent cbe81ca commit 5357d15
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 43 deletions.
20 changes: 14 additions & 6 deletions script.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const properties = { // Each cell will have these properties by default
color: "#ccc",
pheromone: 1.0
}
window.startingPoint = 'red';
window.startingPoint = 'red'; // Global color for the starting point
window.grid = [];
for (let x = 0; x < window.gridWidth; x++) {
let cols = [];
Expand All @@ -18,42 +18,48 @@ let start;

document.getElementById("widget_status").textContent = "Detenida";

// Load last used theme if available
if (localStorage.getItem('theme')) { localStorage.getItem('theme') == 'dark' ? applyTheme(1) : applyTheme(); } else { applyTheme(darkThemeMq.matches); }

window.onload = function () {
drawElements(scenarios[getSelectedScenario()])
drawElements(scenarios[getSelectedScenario()]) // Initial drawing
darkThemeMq.addEventListener("change", e => {
applyTheme(e.matches);
localStorage.setItem('theme', e.matches ? 'dark' : 'light');
});

canvas.addEventListener("click", function (event) {
const rect = canvas.getBoundingClientRect();
let clickedOnFloor = true;
start = {
x: Math.floor((event.clientX - rect.left) / window.cellSize),
y: Math.floor((event.clientY - rect.top) / window.cellSize)
};
document.getElementById("widget_status").textContent = "Detenida";
let state = true;
for (let i = start.x - 1; i <= start.x + 1; i++) {
for (let j = start.y - 1; j <= start.y + 1; j++) {
if (window.grid[i][j].color != "#ccc") {
state = false;
clickedOnFloor = false;
break;
}
}
}
if (state) {
// If clicked on a valid position
if (clickedOnFloor) {
console.log(`Clicked at (${start.x}, ${start.y})`);
drawElements(scenarios[getSelectedScenario()]);
setColor([start.x - 1, 2], [start.y - 1, 2], window.startingPoint);
} else {

// If clicked on the current starting point
if (window.grid[start.x][start.y].color === window.startingPoint) {
window.showToast("Por favor, seleccione otro punto o inicie la simulación.")
} else { window.showToast("No puedes empezar ahí. Haz clic en el suelo de la habitación."); }
} else { window.showToast("No puedes empezar ahí. Haz clic en el suelo de la habitación."); } // If clicked elsewhere
}
});
document.getElementById("start").addEventListener("click", function () {

// If there is any point set and the simulation is down
if (start && document.getElementById("widget_status").textContent == "Detenida") {
runSimulations(start,
Number(document.getElementById("alpha").value),
Expand All @@ -74,6 +80,8 @@ window.onload = function () {
const containerRect = canvasContainer.getBoundingClientRect();
const selected = getSelectedScenario();
[window.gridWidth, window.gridHeight] = [dimensions[selected].gridWidth, dimensions[selected].gridHeight];

// Set unitary scaling factor (USF)
window.cellSize = Math.min(
Math.floor(containerRect.width / window.gridWidth),
Math.floor(containerRect.height / window.gridHeight)
Expand Down
31 changes: 21 additions & 10 deletions src/ant.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,11 @@ export default class Ant {
for (let i = 0; i < directions.length; i++) {
const euclideanDistance = moduleToGoal(this.x + directions[i].x, this.y + directions[i].y);
const invertedDistance = (Math.abs(directions[i].x) + Math.abs(directions[i].y)) === 2 ? 1 / Math.sqrt(2) : 1;
let weigh = Math.pow(pheromone[i], this.alpha) * Math.pow(invertedDistance, this.beta); // Generic cost calculation formula for Ant Colony Optimization systems
let weigh = Math.pow(pheromone[i], this.alpha) * Math.pow(invertedDistance, this.beta); // Generic weigh calculation formula for Ant Colony Optimization systems

weighs.push(weigh);

// Update best distance
if (euclideanDistance < distance) {
distance = euclideanDistance;
bestDirectionIndex = i;
Expand All @@ -51,14 +52,15 @@ export default class Ant {

weighs[bestDirectionIndex] += this.correctionRatio; // Algorithm improvement aside the original ACO algorithm

// Specific weigh optimization for the algorithm
const lastVisitedCellIndex = this.directions.findIndex(direction =>
this.visited[this.visited.length - 1].x === this.x - direction.x &&
this.visited[this.visited.length - 1].y === this.y - direction.y
);
if (lastVisitedCellIndex > 0) weighs[lastVisitedCellIndex - 1] /= 3;
if (lastVisitedCellIndex < 0) weighs[lastVisitedCellIndex + 1] /= 3;


// Roulette-wheelish selection
const probabilities = calcProbabilities(weighs);
const rand = Math.random();
let cumulative = 0;
Expand All @@ -70,49 +72,58 @@ export default class Ant {
}
}
getDirs(x, y, avoid) {

// Just ensures to take x and y values from somewhere
if (!(x && y)) {
x = this.x;
y = this.y;
}
return this.directions.filter(direction => {
const newX = x + direction.x;
const newY = y + direction.y;
const isObject = this.obstacle.some(object => newX === object.x && newY === object.y);
const isVisited = this.visited.some(visit => newX === visit.x && newY === visit.y);
const isAvoided = avoid && avoid.some(deadEnd => deadEnd.x === newX && deadEnd.y === newY);
const isObject = this.obstacle.some(object => newX === object.x && newY === object.y); // Whether is an object
const isVisited = this.visited.some(visit => newX === visit.x && newY === visit.y); // Whether is has been visited before
const isAvoided = avoid && avoid.some(deadEnd => deadEnd.x === newX && deadEnd.y === newY); // Whether is it marked as a dead end

return !isObject && !isVisited && !isAvoided;
});
}
move(grid, directions, goal) {

// Get the pheromone from every available direction
const total_pheromone = directions.map(direction => {
const inX = this.x + direction.x;
const inY = this.y + direction.y;
return grid[inX][inY].pheromone;
});

const index = this._calcCost(total_pheromone, directions, goal);
const index = this._calcCost(total_pheromone, directions, goal); // This gets which option is the best for the working conditions

// Update new position
const newX = this.x + directions[index].x;
const newY = this.y + directions[index].y;
grid[newX][newY].pheromone += this.deposit;
grid[newX][newY].pheromone += this.deposit; // Add pheromone to the moved cell

// Update ant's position and visited path
this.x = newX;
this.y = newY;
const distance = (Math.abs(directions[index].x) + Math.abs(directions[index].y)) === 2 ? Math.sqrt(2) : 1;
const distance = (Math.abs(directions[index].x) + Math.abs(directions[index].y)) === 2 ? Math.sqrt(2) : 1; // If the direction moves in both x and y axis, then the distance is root of two. If not, the distance is one
return [{ x: newX, y: newY }, distance];
}
revertMove() {
const deadEnd = this.visited.pop(); // Remove from visited the last element
const deadEnd = this.visited.pop(); // Remove the last element from the ant's trace
const { x, y } = this.visited[this.visited.length - 1]; // Get the new last element
return { x: x, y: y, avoid: deadEnd };
}
checkExit(grid, state) {
const color = state ? "#02b200" : "red";
const color = state ? "#02b200" : "red"; // Tells to look for the exit or for the starting point
let isExit = false;

// If any of the next cells looks like an exit, returns true and finishes the loop
for (const direction of this.directions) {
const exitX = this.x + direction.x;
const exitY = this.y + direction.y;

if (grid[exitX][exitY].color === color) {
isExit = true;
break;
Expand Down
2 changes: 2 additions & 0 deletions src/layouts.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,8 @@ export function getSelectedScenario() { return document.querySelector('input[nam
document.getElementById('scenarios-form').addEventListener('change', () => {
const selected = getSelectedScenario();
[window.gridWidth, window.gridHeight] = [dimensions[selected].gridWidth, dimensions[selected].gridHeight];

// Set unitary scaling factor (USF)
window.cellSize = Math.min(
Math.floor(containerRect.width / window.gridWidth),
Math.floor(containerRect.height / window.gridHeight)
Expand Down
68 changes: 41 additions & 27 deletions src/source.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const paint = canvas.getContext("2d");
const [startingAnt, returningAnt] = ["green", "darkgreen"];
let stepNumber = 0;

// This function returns the coordinates of every obstacle in the scenario
function getObjects(object) {
let objects = [];
if (Array.isArray(object)) {
Expand All @@ -22,6 +23,8 @@ function getObjects(object) {
}
return objects;
}

// 'Main' function
async function antStart(state, start, initial, alpha, beta, rho, deposit, objects) {
return new Promise((resolve, reject) => {
const room = scenarios[getSelectedScenario()];
Expand All @@ -30,21 +33,24 @@ async function antStart(state, start, initial, alpha, beta, rho, deposit, object
let { x, y } = initial;
let visited = [{ x, y }];
let deadEnds = [];
let ant = new Ant(x, y, visited, objects, state ? Math.pow(alpha, 2) : alpha, beta, deposit, window.gridWidth, window.gridHeight);
let ant = new Ant(
x, y, // Initial coordinates of the ant
visited, objects, // Different types of elements in the scenario
alpha, beta, deposit, // User-provided heuristic parameters
window.gridWidth, window.gridHeight // Scenario dimensions
);

function moveAnt() {
function getNearestPoint(x0, y0, objects) {
function getNearestExit(x0, y0, objects) {
let nearest = {};
let currentDistance = Infinity;

for (const object of objects) {
const distance = Math.sqrt(Math.pow(object.x - x0, 2) + Math.pow(object.y - y0, 2));
const distance = Math.sqrt(Math.pow(object.x - x0, 2) + Math.pow(object.y - y0, 2)); // Euclidean formula
if (distance < currentDistance) {
nearest = { x: object.x, y: object.y };
currentDistance = distance;
} else {
break;
}
} else { break; }
}
return nearest;
}
Expand All @@ -58,7 +64,7 @@ async function antStart(state, start, initial, alpha, beta, rho, deposit, object
deadEnds.push(revert.avoid);
[x, y] = [revert.x, revert.y];
newdirs = ant.getDirs(x, y, deadEnds);
} while (newdirs.length < 1);
} while (newdirs.length < 1); // Until the ant has an alternative path to follow
ant.x = x;
ant.y = y;
dirs = newdirs;
Expand All @@ -68,16 +74,13 @@ async function antStart(state, start, initial, alpha, beta, rho, deposit, object
const exit = room.exits[key];
exits.push(exit.color);
}
let nearestPoint = getNearestPoint(x, y, getObjects(state ? exits : window.startingPoint));
const [movedTo, distance] = ant.move(window.grid, dirs, nearestPoint);
let nearestExit = getNearestExit(x, y, getObjects(state ? exits : window.startingPoint));
const [movedTo, distance] = ant.move(window.grid, dirs, nearestExit);
moveCount++;
visited.push({ x: movedTo.x, y: movedTo.y });

// Evaporate pheromone
for (const visit of visited) {
window.grid[visit.x][visit.y].pheromone *= (1 - rho);
if (window.grid[visit.x][visit.y].pheromone < 0.00001) window.grid[visit.x][visit.y].pheromone = 0;
}
for (const visit of visited) { window.grid[visit.x][visit.y].pheromone *= (1 - rho); }

[x, y] = [movedTo.x, movedTo.y];
ant.x = movedTo.x;
Expand All @@ -91,7 +94,7 @@ async function antStart(state, start, initial, alpha, beta, rho, deposit, object
for (const visit of visited) setColor(visit.x, visit.y, state ? startingAnt : returningAnt);
setColor([start.x - 1, 2], [start.y - 1, 2], window.startingPoint);

// Speed regulation
// Dynamic speed regulation
if (moveCount % Number(document.getElementById("ant_speed").value) === 0 &&
Number(document.getElementById("ant_speed").value) != Number(document.getElementById("ant_speed").max)) requestAnimationFrame(moveAnt);
else moveAnt(); // Continue the loop
Expand All @@ -103,6 +106,7 @@ async function antStart(state, start, initial, alpha, beta, rho, deposit, object
requestAnimationFrame(moveAnt); // Start the loop
});
}

export async function applyTheme(isDark) {
if (isDark) {
document.body.classList.add('dark-mode');
Expand All @@ -118,16 +122,18 @@ export async function applyTheme(isDark) {
window.applyTheme = applyTheme;
}
export async function setColor(x, y, color) {

// If the parameters are intervals, the function operates with the sum of the first and the second
const X = Array.isArray(x) ? x[0] : x;
const endX = Array.isArray(x) ? x[0] + x[1] : x;
const Y = Array.isArray(y) ? y[0] : y;
const endY = Array.isArray(y) ? y[0] + y[1] : y;
for (let i = X; i <= endX; i++) {
for (let j = Y; j <= endY; j++) {
try {
window.grid[i][j].color = color;
window.grid[i][j].color = color; // Make low-level changes
paint.fillStyle = window.grid[i][j].color;
paint.fillRect(i * window.cellSize, j * window.cellSize, window.cellSize, window.cellSize);
paint.fillRect(i * window.cellSize, j * window.cellSize, window.cellSize, window.cellSize); // Make graphical changes
} catch (error) {
return;
}
Expand All @@ -136,6 +142,8 @@ export async function setColor(x, y, color) {
}
export async function drawElements(room) {
const { floor, walls, windows, exits, elements } = room;

// Iterates one by one and displays the objects respectingly
for (const item in room) {
switch (item) {
case 'floor':
Expand Down Expand Up @@ -210,7 +218,7 @@ export async function runSimulations(start, alpha, beta, rho, deposit, steps) {
let bestPath = [];
document.getElementById("widget_status").textContent = "En ejecucción";

// Reset trace through simulations
// Reset pheromone trace through simulations
for (let i = 0; i < window.gridWidth; i++) {
for (let j = 0; j < window.gridHeight; j++) {
if (window.grid[i][j].pheromone != 1.0) window.grid[i][j].pheromone = 1.0;
Expand All @@ -222,6 +230,7 @@ export async function runSimulations(start, alpha, beta, rho, deposit, steps) {
let stringPath = '';
let objects = [];

// Get the color of every wall, window and all the elements
for (const key in room) {
const element = room[key];
if (key === 'walls' || key === 'windows' || key === 'elements') {
Expand All @@ -237,25 +246,28 @@ export async function runSimulations(start, alpha, beta, rho, deposit, steps) {
}
}
drawElements(scenarios[getSelectedScenario()]);
for (const path of bestPath) { setColor(path.x, path.y, startingAnt) };
for (const path of bestPath) setColor(path.x, path.y, startingAnt);
document.getElementById("widget_step").textContent = `${stepNumber + 1} de ${steps}`;

elements = getObjects(objects);
[currentPoint, newDistance, visited] = await antStart(1, start, currentPoint, alpha, beta, rho, deposit, elements);

// Check if the new distance is lower than the older one stored or stores it if there was no stored distance before
if (newDistance < oldDistance || oldDistance == 0) {
oldDistance = newDistance;
bestPath = visited;
}
for (const path of bestPath) setColor(path.x, path.y, startingAnt);
visited = [];
elements = [];
for (const key in room.exits) {
const exit = room.exits[key];
objects.push(exit.color); // Added all exits as objects for the ant to avoid
}

// Include all exits as objects for the ant to avoid and the returning
for (const key in room.exits) objects.push(room.exits[key].color);

elements = getObjects(objects);
[currentPoint, newDistance, visited] = await antStart(0, start, currentPoint, alpha, beta, rho, deposit, elements);

// Check if the new distance is lower than the older one stored
if (newDistance < oldDistance) {
oldDistance = newDistance;
bestPath = visited;
Expand Down Expand Up @@ -283,7 +295,9 @@ export async function runSimulations(start, alpha, beta, rho, deposit, steps) {
for (const path of bestPath) setColor(path.x, path.y, window.startingPoint);
}
export function roundValues(obj) {
if (typeof obj === 'object' && obj !== null) {

// Iterates on each value to check if it is a number
if (typeof obj === 'object' && obj !== null) { // If it is not a number, the value remains the same
if (Array.isArray(obj)) {
return obj.map(item => roundValues(item));
} else {
Expand All @@ -295,16 +309,16 @@ export function roundValues(obj) {
}
return roundedObj;
}
} else if (typeof obj === 'number') {
} else if (typeof obj === 'number') { // Rounds the value if it is a number
return Math.round(obj);
}

// Returns the number-rounded object
return obj;
}
window.showToast = async function (t) {
const toast = document.getElementById('toast');
toast.textContent = t;
toast.classList.add('show');
setTimeout(() => {
toast.classList.remove('show');
}, 4000);
setTimeout(() => { toast.classList.remove('show'); }, 4000); // Shows toast for 4 secs
};

0 comments on commit 5357d15

Please sign in to comment.