I am trying to get an implementation of Dijkstra's algorithm in JavaScript to run properly.
I found a walkthrough Here and do not understand the return line
return `Path is ${path} and time is ${times[endNode]}
I have collected the code from the link below to make it easier.
class Graph {
constructor() {
this.nodes = [];
this.adjacencyList = [];
}
addNode(node) {
this.nodes.push(node);
this.adjacencyList[node] = [];
}
addEdge(node1, node2, weight) {
this.adjacencyList[node1].push({node:node2, weight: weight});
this.adjacencyList[node2].push({node:node1, weight: weight});
}
findPathWithDijkstra(startNode, endNode) {
let times = [];
let backtrace = [];
let pq = new PriorityQueue();
times[startNode] = 0;
this.nodes.forEach(node => {
if (node !== startNode) {
times[node] = Infinity
}
});
pq.enqueue([startNode, 0]);
while (!pq.isEmpty()) {
let shortestStep = pq.dequeue();
let currentNode = shortestStep[0];
this.adjacencyList[currentNode].forEach(neighbor => {
let time = times[currentNode] + neighbor.weight;
if (time < times[neighbor.node]) {
times[neighbor.node] = time;
backtrace[neighbor.node] = currentNode;
pq.enqueue([neighbor.node, time]);
}
});
}
let path = [endNode];
let lastStep = endNode;
while(lastStep !== startNode) {
path.unshift(backtrace[lastStep])
lastStep = backtrace[lastStep]
}
return `Path is ${path} and time is ${times[endNode] }`
}
}
class PriorityQueue {
constructor() {
this.collection = [];
}
enqueue(element){
if (this.isEmpty()){
this.collection.push(element);
} else {
let added = false;
for (let i = 1; i <= this.collection.length; i++){
if (element[1] < this.collection[i-1][1]){
this.collection.splice(i-1, 0, element);
added = true;
break;
}
}
if (!added){
this.collection.push(element);
}
}
};
dequeue() {
let value = this.collection.shift();
return value;
};
isEmpty() {
return (this.collection.length === 0)
};
}
let testGraph01 = new Graph();
let testGraph02 = new Graph();
let testGraph03 = new Graph();
//creating graphs
testGraph01.addNode("A");
testGraph01.addNode("B");
testGraph01.addNode("C");
testGraph01.addNode("D");
testGraph01.addEdge("A","B",10);
testGraph01.addEdge("C","D",5);
testGraph01.addEdge("D","B",5);
console.log(testGraph01.findPathWithDijkstra("A","D"));
I am looking for an explanation of the templated return. How does it work in javascript i guess?
Related
I have the following bit of code whereby I'm using class expressions
const ShapeOverlays = class {
constructor(elm) {
this.elm = elm;
this.path = elm.querySelectorAll('path');
this.numPoints = 4;
this.duration = 1000;
this.delayPointsArray = [];
this.delayPointsMax = 0;
this.delayPerPath = 60;
this.timeStart = Date.now();
this.isOpened = false;
this.isAnimating = false;
}
}
(function() {
const elmHamburger = document.querySelector('.hamburger');
const gNavItems = document.querySelectorAll('.global-menu__item');
const elmOverlay = document.querySelector('.shape-overlays');
const overlay = new ShapeOverlays(elmOverlay);
elmHamburger.addEventListener('click', () => {
if (overlay.isAnimating) {
return false;
}
overlay.toggle();
if (overlay.isOpened === true) {
elmHamburger.classList.add('is-opened-navi');
for (var i = 0; i < gNavItems.length; i++) {
gNavItems[i].classList.add('is-opened');
}
} else {
elmHamburger.classList.remove('is-opened-navi');
for (var i = 0; i < gNavItems.length; i++) {
gNavItems[i].classList.remove('is-opened');
}
}
});
}());
But I'm getting the error
Uncaught ReferenceError: Cannot access 'ShapeOverlays' before initialization
on the line const overlay = new ShapeOverlays(elmOverlay); which is weird because the class has been initialized above. What I'm I doing wrong? Thanks.
Your class expression is missing a semicolon.
const ShapeOverlays = class {
constructor(elm) {
// ...
}
}; // <-- this one
Working:
const Test = class {
constructor() {
this.isOpened = false;
this.isAnimating = false;
}
};
(function() {
const overlay = new Test(4);
console.log(overlay)
}());
Not working:
const Test = class {
constructor() {
this.isOpened = false;
this.isAnimating = false;
}
}
(function() {
const overlay = new Test(4);
console.log(overlay)
}());
you have one too may (...). So, if you moved it to the following I think it should work fine.
const ShapeOverlays = class {
constructor(elm) {
this.elm = elm;
this.path = elm.querySelectorAll("path");
this.numPoints = 4;
this.duration = 1000;
this.delayPointsArray = [];
this.delayPointsMax = 0;
this.delayPerPath = 60;
this.timeStart = Date.now();
this.isOpened = false;
this.isAnimating = false;
}
};
(function () {
const elmHamburger = document.querySelector(".hamburger");
const gNavItems = document.querySelectorAll(".global-menu__item");
const elmOverlay = document.querySelector(".shape-overlays");
const overlay = new ShapeOverlays(elmOverlay);
elmHamburger.addEventListener("click", () => {
if (overlay.isAnimating) {
return false;
}
overlay.toggle();
if (overlay.isOpened === true) {
elmHamburger.classList.add("is-opened-navi");
for (var i = 0; i < gNavItems.length; i++) {
gNavItems[i].classList.add("is-opened");
}
} else {
elmHamburger.classList.remove("is-opened-navi");
for (var i = 0; i < gNavItems.length; i++) {
gNavItems[i].classList.remove("is-opened");
}
}
});
})();
Let me know if it does NOT help. Thanks.
It works if you declare the class inside the function, its happening because you are instantly declaring and invoking the function so the scope is lexical in terms to the globally declared class. See this for reference:https://developer.mozilla.org/en-US/docs/Glossary/IIFE
(function() {
const ShapeOverlays = class {
constructor(elm) {
this.elm = elm;
this.path = elm.querySelectorAll('path');
this.numPoints = 4;
this.duration = 1000;
this.delayPointsArray = [];
this.delayPointsMax = 0;
this.delayPerPath = 60;
this.timeStart = Date.now();
this.isOpened = false;
this.isAnimating = false;
}
}
const elmHamburger = document.querySelector('.hamburger');
const gNavItems = document.querySelectorAll('.global-menu__item');
const elmOverlay = document.querySelector('.shape-overlays');
const overlay = new ShapeOverlays(elmOverlay);
elmHamburger.addEventListener('click', () => {
if (overlay.isAnimating) {
return false;
}
overlay.toggle();
if (overlay.isOpened === true) {
elmHamburger.classList.add('is-opened-navi');
for (var i = 0; i < gNavItems.length; i++) {
gNavItems[i].classList.add('is-opened');
}
} else {
elmHamburger.classList.remove('is-opened-navi');
for (var i = 0; i < gNavItems.length; i++) {
gNavItems[i].classList.remove('is-opened');
}
}
});
}());
I've implemented BST to compare to others objects types, as Map, {} and Array.
But the weird thing is that insertion in BST takes too long, but search and delete operations in BST is faster than in any other data type.
I'm guessing that is due to many instances of Node, but i don't even sure if that makes sense..
export class BinarySearchTree{
constructor(root = null){
this.root = root;
this.length = 1;
}
search(key){
let cur_node = this.root;
while(true){
if(cur_node === null || cur_node === undefined) return null;
if(cur_node.key === key) return cur_node;
if(key < cur_node.key) cur_node = cur_node.left;
else cur_node = cur_node.right;
}
}
add(key, value = key){
const new_node = new Node(key, value);
let cur_node = this.root;
let parent_node = null;
if(!this.root){
this.root = new_node;
return;
}
while(true){
if(cur_node === null) break;
parent_node = cur_node;
if(parent_node.key === key) return;
if(key < cur_node.key) cur_node = cur_node.left;
else cur_node = cur_node.right;
}
const side = key < parent_node.key ? 'left' : 'right';
parent_node[side] = new_node;
this.length++;
}
delete(key){
let cur_node = this.root;
let parent_node = null;
while(true){
if(cur_node === null || cur_node.key === key) break;
parent_node = cur_node;
if(key < cur_node.key) cur_node = cur_node.left;
else cur_node = cur_node.right;
}
let children = cur_node.getChildren();
const side = parent_node !== null ? parent_node.getChildByKey(key)[0] : 'root';
if(children.length === 0) parent_node[side] = null;
else if(children.length === 1) parent_node[side] = children[0][1];
else if(children.length === 2){
const min_node_map = this.minNode(cur_node.right);
cur_node.key = min_node_map.get('min').key;
cur_node.value = min_node_map.get('min').value;
min_node_map.get('parent').left = null;
}
this.length--;
}
minNode(node = this.root){
let parent_node = null;
let cur_node = node;
while(cur_node.left) {
parent_node = cur_node;
cur_node = cur_node.left;
}
const map = new Map();
map.set('min', cur_node);
map.set('parent', parent_node);
return map;
}
}
export class Node{
constructor(key, value){
this.key = key;
this.value = value;
this.left = null;
this.right = null;
}
getChildByKey(key){
return this.getChildren().find(x => x[1].key === key);
}
getChildren(){
const map = new Map();
map.set('left', this.left);
map.set('right', this.right);
return [...map].filter(x => x[1] !== null);
}
}
import { BinarySearchTree } from "./binary-search-tree.js";
const INTERACTIONS = 999999;
//random keys
const random_values = new Array(INTERACTIONS);
for(let i = 0; i < INTERACTIONS; i++)random_values[i] = random(0, INTERACTIONS);
//just to test SEARCH
const SPECIAL_KEY = 1234567;
random_values[Math.floor((random_values.length - 1)/2)] = SPECIAL_KEY;
const item = 'any constant data';
const show = () => {
let arr = [];
console.log('array');
performance('insert', () => {
for(let i = 0; i < INTERACTIONS; i++)
arr.push(random_values[i])
});
performance('search', () => arr.find(x => x === SPECIAL_KEY));
performance('delete', () => arr.splice(arr.indexOf(SPECIAL_KEY), 1));
const obj = {};
console.log(`obj`);
performance('insert', () => {
for(let i = 0; i < INTERACTIONS; i++)
obj[random_values[i]] = item
});
performance('search', () => obj[SPECIAL_KEY]);
performance('delete', () => delete obj[SPECIAL_KEY]);
const map = new Map();
console.log(`map`);
performance('insert', () => {
for(let i = 0; i < INTERACTIONS; i++)
map.set(random_values[i], item)
});
performance('search', () => map.get(SPECIAL_KEY));
performance('delete', () => map.delete(SPECIAL_KEY));
const bst = new BinarySearchTree();
console.log(`binary search tree`);
performance('insert', () => {
for(let i = 0; i < INTERACTIONS; i++)
bst.add(random_values[i], item)
});
performance('search', () => bst.search(SPECIAL_KEY).value);
performance('delete', () => bst.delete(SPECIAL_KEY));
}
show();
function random(min, max){
return Math.floor(Math.random() * (max - min) + min);
}
function performance(title, callback){
const start_time = process.hrtime();
callback();
const end_time = process.hrtime(start_time);
console.log(`${title} ending... ${end_time} seconds`);
}
This question already has answers here:
Shallow-clone a Map or Set
(6 answers)
Closed 2 years ago.
How can I deep copy a map object to another map ? I'm trying to use ES6 method but this return Map(0){} empty object. My first try was send a map object to function and assign this to a new value in this class. But on next() method I'm removing one property from this map and this change map in unit test file
creatureTurnQueue.js
export default class CreatureTurnQueue {
constructor() {
this.creatureMap = new Map();
this.creatureArray = [];
this.observersArray = [];
}
initQueue(list = {}) {
this.creatureMap = Object.assign({}, list)
this.creatureMap = list //<=old method
list.forEach(val => this.creatureArray.push(val));
}
getActiveCreature() {
let [first] = this.creatureArray.filter(el => el);
return first;
}
next(list = {}) {
this.creatureMap.delete(this.creatureMap.keys().next().value);
if (this.creatureMap.size == 0) {
this.notifyObserver();
this.initQueue(list);
return true;
}
}
addObserver(_observer) {
this.observersArray.push(_observer)
}
removeObserver(_observer) {
this.observersArray.pull(_observer)
}
notifyObserver() {
this.observersArray.forEach(item => item.resetCounterAttack())
}
}
creatureTurnQueueTest.js
import Creature from "../creature.js";
import CreatureTurnQueue from "../creatureTurnQueue.js";
import Point from './../point';
export default class CreatureTurnQueueTest {
queueShoulChangeActiveCreature() {
let creatureTurnQueue = new CreatureTurnQueue();
let creture1 = new Creature("aaaa", 1, 1, 1, 1);
let creture2 = new Creature("bbbb", 1, 1, 1, 1);
let creture3 = new Creature("cccc", 1, 1, 1, 1);
let point1 = new Point(1, 0)
let point2 = new Point(2, 0)
let point3 = new Point(3, 0)
let creatureMap = new Map();
creatureMap.set(point1, creture1);
creatureMap.set(point2, creture2);
creatureMap.set(point3, creture3);
creatureTurnQueue.initQueue(creatureMap);
console.log("~ creatureMap", creatureMap) <= map have 2 elements
creatureMap.forEach(item => { <= creatureMap return 2 elements becouse this one value is removed
if (item !== creatureTurnQueue.getActiveCreature()) {
console.log("~ item", item)
console.log("~ creatureTurnQueue.getActiveCreature()", creatureTurnQueue.getActiveCreature())
throw `Exception: => Kolejka nie dziala poprawnie zwracana aktywna creatura jest inna`;
}
if (creatureTurnQueue.next(creatureMap)) {
throw `Exception: => Kolejka nie dziala poprawnie w momecie wywolania funkcji next()`;
}
});
}
}
The rest of a classes
point.js
export default class Point {
constructor(_x, _y) {
this.x = _x;
this.y = _y;
}
}
creature.js
import CreatureStatistics from "./creatureStatistics.js";
export default class Creature {
constructor(_name, _attack, _armor, _maxHp, _moveRange) {
this.stats = this.createCreature(_name, _attack, _armor, _maxHp, _moveRange);
this.stats.currentHp = this.stats.maxHp;
this.stats.wasCounterAttack = false;
}
createCreature(_name, _attack, _armor, _maxHp, _moveRange) {
return new CreatureStatistics(
_name || "Smok",
_attack || 1,
_armor || 1,
_maxHp || 100,
_moveRange || 10
);
}
setDefaultStats() {
// this.stats.wasCounterAttack == true ? this.stats.wasCounterAttack = false : this.stats.wasCounterAttack = true
this.stats.currentHp = this.stats.currentHp != undefined ? this.stats.currentHp : this.stats.maxHp;
}
// popraw counter atack
attack(_defender) {
_defender.setDefaultStats();
this.setDefaultStats();
if (_defender.isAlive()) {
_defender.stats.currentHp = this.calculateDamage(_defender);
if (_defender.isAlive() && !_defender.stats.wasCounterAttack) {
_defender.stats.wasCounterAttack = true;
this.stats.currentHp = _defender.calculateDamage(this);
}
}
}
calculateDamage(attackedCreature) {
return attackedCreature.stats.currentHp - this.stats.getAttack() + attackedCreature.stats.getArmor() > attackedCreature.stats.getMaxHp()
? attackedCreature.stats.currentHp
: attackedCreature.stats.currentHp - this.stats.getAttack() + attackedCreature.stats.getArmor();
}
isAlive() {
if (this.stats.currentHp > 0) {
return true;
}
}
getCurrentHp() {
return this.stats.currentHp;
}
resetCounterAttack() {
this.stats.wasCounterAttack = false;
}
canCounterAttack() {
return !this.stats.wasCounterAttack
}
}
creatureStatistics.js
export default class CreatureStatistics {
constructor(_name, _attack, _armor, _maxHp, _moveRange) {
this.name = _name;
this.attack = _attack;
this.armor = _armor;
this.maxHp = _maxHp;
this.moveRange = _moveRange;
}
getName() {
return this.name;
}
getAttack() {
return this.attack;
}
getArmor() {
return this.armor;
}
getMaxHp() {
return this.maxHp;
}
getMoveRange() {
return this.moveRange;
}
}
The map was in fact a reference of the creatureMap from unit test script. Modifying the map inside your class will modify the creatureMap in unit test. You can try to create a new Map and copy over all values:
// Start class CreateTurnQueue
initQueue(list = {}) {
const newMap = new Map();
// Both Object and Map has entries method although the order is different
const iterator = list.entries();
for(const item of iterator) {
const [point, creature] = item;
newMap.set(point, creature);
this.creatureArray.push(creature);
}
this.creatureMap = newMap;
}
// End class CreateTurnQueue
Now you don't have a reference to the creatureMap from the unit test script and modify this.creatureMap will not affect the one you passed to the initQueue method as argument.
I am trying to code an evolutionary neural network for the game snake. I already coded the neural network part and now I'd like to output the game of the best individual of every generation. For that I'm using the drawing library p5.js (https://p5js.org/).
In my code I am running a loop in where a new generation based on the last generation is created. Each individual of the generation will have to play the game and that is how they are rated. Now I want the best individual to be outputted after I let every individual play once.
Between each outputted turn of the game of the best individual I want the code to wait 500 milliseconds. How do I achieve that?
Here is the code I've already tried but here it only outputed the board after the last turn of each generation:
async function start() {
for (let i = 0; i < 50; i++) {
population.createNewGeneration();
let bestGameTurns = population.bestIndividual.game.turns; //Array of boards
for (let turn = 0; turn < bestGameTurns.length; turn++) {
let board = bestGameTurns[turn];
drawBoard(board);
let p = new Promise(resolve => setTimeout(resolve, 500));
await p;
function drawBoard(board) {
//Draw the board using p5.js rect()'s
}
}
}
}
Another version but the waiting didn't work here:
let i = 0;
setInterval(async () => {
population.createNewGeneration();
console.log(i, population.avgFitness);
let bestGameTurns = population.bestIndividual.game.turns; //Array of boards
for (let turn = 0; turn < bestGameTurns.length; turn++) {
let board = bestGameTurns[turn];
drawBoard(board);
let p = new Promise(resolve => setTimeout(resolve, 500));
await p;
function drawBoard(board) {
//Draw the board using p5.js rect()'s
}
}
i++;
}, 1);
The code you provided should do what you asked for, I could only clean up some parts for you. Explain a bit better what is the problem you are facing.
// The function should be defined only once.
function drawBoard(board) { }
async function start() {
for (let i = 0; i < 50; i++) {
population.createNewGeneration();
const bestGameTurns = population.bestIndividual.game.turns; //Array of boards
for (let turn = 0; turn < bestGameTurns.length; turn++) {
// Don't wait on first iteration
await new Promise(resolve => setTimeout(resolve, 500 * (turn ? 0 : 1 )));
drawBoard(bestGameTurns[turn]);
}
}
}
Original idea (discarded)
You can create a short function like that:
function pause(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
Then in any async function you can call it like that:
async function () {}
// something happening
await pause(500);
// continue
}
The other idea
Now, the code in your question is not complete so this is kind of blind coding but.
So, first of all setInterval will be running the whole function every 1 millisecond (actually 4 ms, as this is the minimum in JS). Which means it will run those loops. I decided to focus on the part that was marked by you.
Instead of loop and trying to pause it, I ask that maybe rewriting the loop into function called each half second until condition is met would do?
Also, I move drawBoard outside
setInterval(async () => {
// ^^^^^^^^ <-- this should probably go
population.createNewGeneration();
console.log(i, population.avgFitness);
let bestGameTurns = population.bestIndividual.game.turns; //Array of boards
function tick(turn = 0) {
let board = bestGameTurns[turn];
function drawBoard(board) {
//Draw the board using p5.js rect()'s
}
drawBoard(board);
// here is "setTimeouted" loop
if (turn < bestGameTurns.length) {
setTimeout(tick, 500, turn + 1);
}
}
tick();
}, 1);
Thanks to everyone, your suggestions brought me to an idea. I found out that the problem was lying somewhere else. Because javascript only runs on the one thread (I think thats how its called), after we run over one generation, we have to stop that function to let another draw function, which runs every frame, draw the board. After it is drawn, the main function can continue. This is how it looks:
let isDrawn = false;
let currentBoard;
async function setup() {
for (let i = 0; i < 50; i++) {
population.createNewGeneration();
const bestGameTurns = population.bestIndividual.game.turns;
for (let turn = 0; turn < bestGameTurns.length; turn++) {
await step(bestGameTurns[turn], turn);
}
}
}
function step(board, turn) {
currentBoard = board;
isDrawn = false;
return new Promise(resolve => setTimeout(() => {
if (isDrawn) resolve();
}, 500));
}
setTimeout(() => {
if (currentBoard) {
drawBoard(currentBoard);
isDrawn = true;
currentBoard = undefined;
}
}, 1);
const nrOfCols = 10;
const nrOfRows = 10;
const fieldWidth = 20;
const nodeNrs = [24, 8, 8, 4];
const populationSize = 200;
const mutationRate = 0.01;
let population;
let game;
let isDrawn = false;
let currentBoard;
async function setup() {
createCanvas(500, 500);
population = new PopulationManager(populationSize);
for (let i = 0; i < 50; i++) {
population.createNewGeneration();
const bestGameTurns = population.bestIndividual.game.turns;
for (let turn = 0; turn < bestGameTurns.length; turn++) {
await step(bestGameTurns[turn]);
}
}
}
function step(board) {
currentBoard = board;
isDrawn = false;
return new Promise(resolve => setTimeout(() => {
if (isDrawn) resolve();
}, 500));
}
function draw() {
if (currentBoard) {
drawBoard(currentBoard);
isDrawn = true;
currentBoard = undefined;
}
}
function drawBoard(board) {
board.forEach((col, colNr) => {
col.forEach((field, rowNr) => {
fill(field.isSnake ? "green" : field.isFruit ? "red" : "black");
stroke(255);
rect(colNr*fieldWidth, rowNr*fieldWidth, fieldWidth, fieldWidth);
});
});
}
function play(game) {
setInterval(() => {
if (!game.lost) {
game.nextTurn();
drawBoard(game.board);
} else {
clearInterval(1);
}
}, 500);
}
class PopulationManager {
constructor(populationSize) {
this.population = createPopulation();
function createPopulation() {
let population = [];
for (let i = 0; i < populationSize; i++) {
let chromosomes = createRandomChromosomes();
let i = new Individual(chromosomes);
population.push(i);
}
return population;
function createRandomChromosomes() {
let arr = [];
let nrOfChromosomes = calcNrOfChromosomes();
for (let i = 0; i < nrOfChromosomes; i++)
arr.push(Math.random()*2-1);
return arr;
function calcNrOfChromosomes() {
let nr = 0;
for (let i = 0; i < nodeNrs.length - 1; i++)
nr += (nodeNrs[i] + 1)*nodeNrs[i + 1];
return nr;
}
}
};
}
createNewGeneration() {
let that = this;
getFitnessOfPop();
this.calcAvgFitness();
this.findBestIndividual();
let parents = selection();
breed(parents);
function getFitnessOfPop() {
that.population.forEach(iv => {
iv.fitness = iv.playGame();
});
that.population.sort((a, b) => a.fitness - b.fitness);
}
function selection() {
let totalFitness = that.population.map(iv => iv.fitness/* + 1 */).reduce((a,b) => a + b);
let allParents = [];
for (let i = 0; i < that.population.length/2; i++) {
allParents.push(selectRandomParents());
}
return allParents;
function selectRandomParents() {
let p1, p2;
do {
p1 = selectRandomParent();
p2 = selectRandomParent();
} while (p1 == p2);
return [p1, p2];
function selectRandomParent() {
let rdm = Math.random()*totalFitness;
return that.population.find((iv, i) => {
let sum = that.population.filter((iv2, i2) => i2 <= i).map(iv => iv.fitness /* + 1 */).reduce((a,b) => a + b);
return rdm <= sum;
});
}
}
}
function breed(allParents) {
that.population = [];
allParents.forEach(ps => {
let childChromosomes = crossOver(ps[0].chromosome, ps[1].chromosome);
childChromosomes = [mutate(childChromosomes[0]), mutate(childChromosomes[1])];
let child1 = new Individual(childChromosomes[0]);
let child2 = new Individual(childChromosomes[1]);
that.population.push(child1);
that.population.push(child2);
});
function crossOver(parent1Chromosome, parent2Chromosome) {
let crossingPoint = Math.round(Math.random()*parent1Chromosome.length);
let divided1 = divideChromosome(parent1Chromosome);
let divided2 = divideChromosome(parent2Chromosome);
let child1Chromosome = divided1[0].concat(divided2[1]);
let child2Chromosome = divided2[0].concat(divided1[1]);
return [child1Chromosome, child2Chromosome];
function divideChromosome(chromosome) {
let part1 = chromosome.filter((g, i) => i <= crossingPoint);
let part2 = chromosome.filter((g, i) => i > crossingPoint);
return [part1, part2];
}
}
function mutate(chromosome) {
chromosome = chromosome.map(g => {
if (Math.random() < mutationRate)
return Math.random()*2-1;
return g;
});
return chromosome;
}
}
}
calcAvgFitness() {
this.avgFitness = this.population.map(iv => iv.fitness).reduce((a, b) => a + b) / this.population.length;
}
findBestIndividual() {
let bestFitness = -1, bestIndividual;
this.population.forEach(i => {
if (i.fitness > bestFitness) {
bestFitness = i.fitness;
bestIndividual = i;
}
});
this.bestIndividual = bestIndividual;
}
}
class Individual {
constructor(chromosome) {
this.chromosome = chromosome;
this.fitness = 0;
this.game = createGame();
function createGame() {
let weights = convertChromosomeToWeights();
let game = new Game(weights);
return game;
function convertChromosomeToWeights() {
let weights = [];
for (let i = 0; i < nodeNrs.length - 1; i++) {
let lArr = [];
for (let j = 0; j < nodeNrs[i] + 1; j++) {
let nArr = [];
lArr.push(nArr);
}
weights.push(lArr);
}
chromosome.forEach((gene, geneIdx) => {
let lIdx = -1, minIdx, maxIdx = 0;
for (let i = 0; i < nodeNrs.length - 1; i++) {
let nr = 0;
for (let j = 0; j <= i; j++)
nr += (nodeNrs[j] + 1)*nodeNrs[j + 1];
if (geneIdx < nr) {
lIdx = i;
break;
}
maxIdx = nr;
minIdx = maxIdx;
}
minIdx = maxIdx;
let nIdx = -1;
for (let i = 0; i < nodeNrs[lIdx] + 1; i++) {
let nr = minIdx + nodeNrs[lIdx + 1];;
if (geneIdx < nr) {
nIdx = i;
break;
}
maxIdx = nr;
minIdx = maxIdx;
}
minIdx = maxIdx;
let wIdx = -1;
for (let i = 0; i < nodeNrs[lIdx + 1]; i++) {
let nr = minIdx + 1;
if (geneIdx < nr) {
wIdx = i;
break;
}
maxIdx = nr;
minIdx = maxIdx;
}
weights[lIdx][nIdx][wIdx] = gene;
});
return weights;
}
}
}
playGame() {
while (!this.game.lost) {
this.game.nextTurn();
}
return this.game.score;
}
}
class Game {
constructor(weights) {
let that = this;
this.chromosome = flattenArray(weights);
this.nn = new NeuralNetwork(weights);
this.turnNr = 0;
this.score = 0;
this.lost = false;
this.board = createBoard();
this.snake = new Snake();
setupSnake();
this.createFruit();
this.turns = [JSON.parse(JSON.stringify(this.board))];
function createBoard() {
let board = [];
for (let colNr = 0; colNr < nrOfCols; colNr++) {
board[colNr] = [];
for (let rowNr = 0; rowNr < nrOfRows; rowNr++) {
let field = new Field(colNr, rowNr);
board[colNr][rowNr] = field;
}
}
return board;
}
function setupSnake() {
for (let i = 0; i < 4; i++)
that.addToTail([floor(nrOfCols/2) - i, floor(nrOfRows/2)]);
that.length = that.snake.body.length;
}
function flattenArray(arr) {
let flattened = [];
flatten(arr);
return flattened;
function flatten(arr) {
arr.forEach(e => {
if (Array.isArray(e))
flatten(e);
else
flattened.push(e);
});
}
}
}
addToTail(pos) {
this.snake.body.push(pos);
this.board[pos[0]][pos[1]].setSnake(true);
}
nextTurn() {
let that = this;
let direction = findDirection();
this.moveSnake(direction);
this.turns.push(JSON.parse(JSON.stringify(this.board)));
this.turnNr++;
checkEat();
function findDirection() {
let inputValues = [];
for (let i = 0; i < 8; i++) {
let distances = that.snake.look(i, that.board);
inputValues.push(distances.distToFruit);
inputValues.push(distances.distToWall);
inputValues.push(distances.distToBody);
}
let output = that.nn.getOutput(inputValues);
let probability = -1;
let direction = -1;
output.forEach((v, vIdx) => {
if (v > probability) {
probability = v;
direction = vIdx;
}
});
return direction;
}
function checkEat() {
let head = that.snake.body[0];
let headField = that.board[head[0]][head[1]];
if (headField.isFruit) {
that.snake.eat();
that.score++;
headField.setFruit(false);
that.createFruit();
}
}
}
createFruit() {
let field;
do {
let colNr = floor(random()*nrOfCols);
let rowNr = floor(random()*nrOfRows);
field = this.board[colNr][rowNr];
} while(field.isSnake);
field.setFruit(true);
}
moveSnake(newDirection) {
let that = this;
let oldBody = JSON.parse(JSON.stringify(that.snake.body));
moveTail();
makeSnakeLonger();
moveHead();
function moveTail() {
for (let i = oldBody.length - 1; i > 0; i--)
that.snake.body[i] = oldBody[i - 1];
}
function moveHead() {
let newHeadPosition = findNewHeadPos();
if (
newHeadPosition[0] >= nrOfCols || newHeadPosition[0] < 0 ||
newHeadPosition[1] >= nrOfRows || newHeadPosition[1] < 0
) {
that.lose();
return;
}
let newHeadField = that.board[newHeadPosition[0]][newHeadPosition[1]];
if (newHeadField.isSnake) {
that.lose();
return;
}
addNewHeadPos(newHeadPosition);
}
function findNewHeadPos() {
if (newDirection == 0) { //up
return [oldBody[0][0], oldBody[0][1] - 1];
} else if (newDirection == 1) { //right
return [oldBody[0][0] + 1, oldBody[0][1]];
} else if (newDirection == 2) { //down
return [oldBody[0][0], oldBody[0][1] + 1];
} else if (newDirection == 3) { //left
return [oldBody[0][0] - 1, oldBody[0][1]];
}
}
function makeSnakeLonger() {
if (that.snake.length > that.snake.body.length) {
that.addToTail(oldBody[oldBody.length - 1]);
} else {
removeFromTail(oldBody[oldBody.length - 1]);
}
}
function removeFromTail(pos) {
that.board[pos[0]][pos[1]].setSnake(false);
}
function addNewHeadPos(pos) {
that.snake.body[0] = pos;
that.board[pos[0]][pos[1]].setSnake(true);
}
}
lose() {
this.lost = true;
}
}
class Field {
constructor(col, row) {
this.col = col;
this.row = row;
this.isFruit = false;
this.isSnake = false;
}
setFruit(bool) {
this.isFruit = bool;
}
setSnake(bool) {
this.isSnake = bool;
}
}
class Snake {
constructor() {
this.body = [];
this.length = 4;
}
eat() {
this.length++;
}
look(direction, board) {
let distances = {distToFruit: 0, distToWall: 0, distToBody: 0};
let xDiff = getXDiff(direction), yDiff = getYDiff(direction);
let pos = [this.body[0][0] + xDiff, this.body[0][1] + yDiff];
let dist = 1;
while (pos[0] > 0 && pos[0] < nrOfRows && pos[1] > 0 && pos[1] < nrOfCols) {
if (board[pos[0]][pos[1]].isFruit && distances.distToFruit == 0) distances.distToFruit = dist;
if (board[pos[0]][pos[1]].isSnake && distances.distToBody == 0) distances.distToBody = dist;
pos[0] += xDiff, pos[1] += yDiff;
dist++;
}
distances.distToWall = dist;
return distances;
function getXDiff(direction) {
if (direction == 5 || direction == 6 || direction == 7) return -1;
else if (direction == 1 || direction == 2 || direction == 3) return 1;
return 0;
}
function getYDiff(direction) {
if (direction == 7 || direction == 0 || direction == 1) return -1;
else if (direction == 3 || direction == 4 || direction == 5) return 1;
return 0;
}
}
}
class NeuralNetwork {
constructor(weights) {
this.layers = createLayers();
this.layers = addWeights(this.layers, weights);
function createLayers() {
let layers = [];
let nrOfNodesGlobal;
nodeNrs.forEach((nrOfNodes, lNr) => {
nrOfNodesGlobal = nrOfNodes;
layers[lNr] = [];
for (let i = 0; i < nrOfNodes; i++) {
let node = createNode(lNr);
layers[lNr][i] = node;
}
if (lNr != nodeNrs.length - 1)
layers[lNr].push(new Bias());
});
return layers;
function createNode(lNr) {
if (lNr == 0) return new InputLayerNode();
else if (lNr == nrOfNodesGlobal - 1) return new OutputLayerNode();
else return new HiddenLayerNode();
}
}
function addWeights(layers, weights) {
for (let lNr = 0; lNr < layers.length - 1; lNr++) {
let l = layers[lNr];
l.forEach((n1, nNr) => {
for (let n2Nr = 0; n2Nr < layers[lNr+1].length - 1; n2Nr++) { //not including bias of next layer
let n2 = layers[lNr+1][n2Nr];
let weight = weights[lNr][nNr][n2Nr];
let w = new Weight(n1, n2, weight);
n1.addWeight(w);
}
});
}
return layers;
}
}
getOutput(inputValues) {
let output = [];
this.layers[0].forEach((inputNeuron, nNr) => {
if (nNr != this.layers[0].length - 1)
inputNeuron.addToInput(inputValues[nNr]);
});
this.layers.forEach((l, lNr) => {
calcOutputs(l);
if (lNr != this.layers.length - 1) {
l.forEach(n => {
n.feedForward();
});
} else {
output = l.map(n => n.output);
}
});
return output;
function calcOutputs(layer) {
layer.forEach(n => n.output = n.activationFunction(n.summedInput, layer.map(N => N.summedInput)));
}
}
log() {
console.log(this.weights, this.nodes);
}
}
class Node {
constructor() {
this.weights = [];
this.summedInput = 0;
}
addWeight(w) {
this.weights.push(w);
}
addToInput(input) {
if (input == NaN)
console.log("A");
this.summedInput += input;
}
feedForward() {
this.weights.forEach((w, wNr) => {
let input = w.weight*this.output;
w.to.addToInput(input);
});
}
}
class Bias extends Node {
constructor() {
super();
this.output = 1;
}
activationFunction(x, allXs) {
return x;
}
}
class InputLayerNode extends Node {
constructor() {
super();
}
activationFunction(x, allXs) {
return x;
}
}
class HiddenLayerNode extends Node {
constructor() {
super();
}
activationFunction(x, allXs) {
return leakyReLU(x);
}
}
class OutputLayerNode extends Node {
constructor() {
super();
}
activationFunction(x, allXs) {
return softmax(x, allXs);
}
}
class Weight {
constructor(from, to, weight) {
this.from = from;
this.to = to;
this.weight = weight;
}
setWeight(newWeight) {
this.weight = weight;
}
}
function leakyReLU(x) {
if (x >= 0) return x;
else return 0.01*x;
}
function softmax(x, allXs) {
return Math.exp(x) / allXs.map(X => Math.exp(X)).reduce((a, b) => a+b);
}
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/p5.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/addons/p5.dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/addons/p5.sound.min.js"></script>
<link rel="stylesheet" type="text/css" href="style.css">
<meta charset="utf-8" />
</head>
<body>
<script src="sketch.js"></script>
</body>
</html>
It's not working that well but a few improvements should make it better...
If you have any suggestions for improvements of the code, please let me know!
I tried to fix it into steps as I said in comments,I hope I have no mistake :
let i = 0;
async function step(bestGameTurns, turn)
{
if (turn == bestGameTurns.length)
return;
let board = bestGameTurns[turn];
drawBoard(board);
let p = new Promise(resolve => setTimeout(() => step(bestGameTurns, turn+1), 500));
await p;
}
function drawBoard(board) {
//Draw the board using p5.js rect()'s
}
setInterval(async () => {
population.createNewGeneration();
console.log(i, population.avgFitness);
let bestGameTurns = population.bestIndividual.game.turns; //Array of boards
step(bestGameTurns, 0);
i++;
}, 1);
I’m trying to rewrite my code to get over a stack size error “RangeError: Maximum call stack size exceeded” I run into. I’m trying to run a DFS using a stack in JavaScript using node.
I’ve heard a lot about settimeout but I’m not sure how to implement it in my case. Any advice would be great.
Heres the code that fails:
var Stack = function() {
this.items = [];
};
Stack.prototype.push = function(obj) {
this.items.push(obj);
};
Stack.prototype.pop = function() {
return this.items.pop();
};
Stack.prototype.isEmpty = function() {
return this.items.length === 0;
};
Stack.prototype.isExplored = function(n){
return this.items.indexOf(n) !== -1;
}
Stack.prototype.emptyOut = function() {
this.items = [];
return this.items.length === 0;
};
var stack = new Stack();
var adjList=[];
for(var i= 1; i<15557;i++){
adjList.push([i])
}
adjList.push([0])
function DFS(graph, s){
stack.push(s);
for(var i = 0 ; i < graph[s].length; i++){
var v = graph[s][i];
if(!stack.isExplored(v)){
DFS(graph, v);
}
}
}
DFS(adjList, 0)
console.log("done", stack);
Have you considered switching to an iterative approach?
var Stack = function() {
this.items = [];
};
Stack.prototype.push = function(obj) {
this.items.push(obj);
};
Stack.prototype.pop = function() {
return this.items.pop();
};
Stack.prototype.isEmpty = function() {
return this.items.length === 0;
};
Stack.prototype.isExplored = function(n) {
return this.items.indexOf(n) !== -1;
}
Stack.prototype.emptyOut = function() {
this.items = [];
return this.items.length === 0;
};
var stack = new Stack();
var adjList = [];
for (var i = 1; i < 15557; i++) {
adjList.push([i]);
}
adjList.push([0])
function DFS(graph, v) {
var queue = [v];
while (queue.length) {
v = queue.pop();
if (stack.isExplored(v))
continue;
stack.push(v);
for (var i = graph[v].length - 1; i >= 0; i--) {
queue.push(graph[v][i]);
}
}
}
DFS(adjList, 0);
console.log("done", stack);
The limit is now the amount of memory node has at disposal.