Add delay when index++ (increase) - javascript

I created a type writer effect with JavaScript but I would like when the word change add delay before the function increase index
const texts = ["Front Developer", "Designer", "Human"];
let count = 0;
let index = 0;
let currentText = '';
let letter = '';
(function type(){
if(count === texts.length){
count = 0;
}
currentText = texts[count];
letter = currentText.slice(0, index++);
document.querySelector('.typing').textContent = letter;
if(letter.length === currentText.length){
count++;
index = 0;
}
setTimeout(type, 400);
}());
<span>I'm <span class="typing"></span></span>

Try return with different timeout
const texts = ["Front Developer", "Designer", "Human"];
let count = 0;
let index = 0;
let currentText = '';
let letter = '';
(function type() {
if (count === texts.length) {
count = 0;
}
currentText = texts[count];
letter = currentText.slice(0, index++);
document.querySelector('.typing').textContent = letter;
if (letter.length === currentText.length) {
count++;
index = 0;
setTimeout(type, 2000);
return;
}
setTimeout(type, 200);
}());
<span>I'm <span class="typing"></span></span>

Here a solution with async / await
const texts = ["Front Developer", "Designer", "Human"];
let count = 0;
let index = 0;
let currentText = '';
let letter = '';
(async function type(){
if(count === texts.length){
count = 0;
}
currentText = texts[count];
letter = currentText.slice(0, index++);
document.querySelector('.typing').textContent = letter;
if(letter.length === currentText.length){
count++;
index = 0;
await sleep(4000);
}
await sleep(30);
type();
}());
function sleep(time) { return new Promise(res => setTimeout(res, time))}
<span>I'm <span class="typing"></span></span>

Related

Problems with calculating total price in cart

I'm trying to calculate the total price of my cart. It works when I only select the same product but when I chose another product it just multiplies the number of clicks to the total price. How can I rewrite this so it doesn't multiply the amount of clicks?
GIF of my problem: https://gyazo.com/c273f1d8a3caa3cded6debef52cfadaf
const shopContainer = document.querySelector(".shop-content");
let productTitle;
let productDescription;
let productImage;
let productPrice;
let productCategory;
let productId;
let productKey = [];
let productArray = [];
let output = "";
const url = "https://fakestoreapi.com/products";
let data = fetch(url)
.then((res) => res.json())
.then((data) => {
for (let i = 0; i < data.length; i++) {
productTitle = data[i].title;
productDescription = data[i].description;
productImage = data[i].image;
productPrice = data[i].price;
productCategory = data[i].category;
productId = data[i].id;
productArray[i] = [
productTitle,
productDescription,
productImage,
productPrice,
productCategory,
productId,
];
productKey[i] = data[i].id;
localStorage.setItem(productKey[i], JSON.stringify(productArray[i]));
}
showApi();
})
.catch((error) => {
console.error("Error message:", error);
});
function showApi() {
for (let i = 0; i < productArray.length; i++) {
productId = productArray[i][5];
output += `
<div class="product-box">
<img class="product" src="${productArray[i][2]}" alt="product image">
<h2 class="product-title">${productArray[i][0]}</h2>
<div class="bottom-box">
<span class="price">${productArray[i][3]}$</span>
<i class='bx bx-shopping-bag add-cart' "data-id="${productId}" onclick="returnKey(${productArray[i][5]})"></i>
</div>
</div>
`;
}
shopContainer.innerHTML = output;
}
let inputCart = document.querySelector(".inputCart");
function returnKey(clickedId) {
cart.classList.add("active");
console.log(clickedId);
if (localStorage.length !== 0) {
let sum = 0;
Object.keys(localStorage).forEach(function (key) {
productObject = JSON.parse(localStorage.getItem(key));
completeProduct = productObject;
sum += completeProduct[3];
if (completeProduct[5] == clickedId) {
let cartPrice = document.createElement("p");
let cartTitle = document.createElement("p");
let cartImage = document.createElement("img");
inputCart.appendChild(cartPrice);
inputCart.appendChild(cartImage);
inputCart.appendChild(cartTitle);
cartPrice.setAttribute("class", "cart-price")
cartTitle.setAttribute("class", "cart-title");
cartImage.setAttribute("src", completeProduct[2]);
cartImage.setAttribute("width", "75");
cartImage.setAttribute("height", "75");
cartTitle.innerHTML = completeProduct[0];
cartPrice.innerHTML = "$" + completeProduct[3];
console.log(cartPrice);
console.log(cartTitle);
document.getElementById('priceTotal').innerHTML = "Total: " + sum + "$";
console.log(sum)
}
})
}
};
// function totalSum(cartPrice) {
// sum = 0;
// cartPrice = document.querySelectorAll(".cart-price");
// for (let i = 0; i < cartPrice.length; i++) {
// sum = sum + completeProduct[3];
// console.log("Product price " + sum);
// document.getElementById('priceTotal').innerHTML = "Total: " + sum + "$";
// }
// }
let removeBtn = document.getElementById("removeBtn").addEventListener("click", clearCart);
let buyBtn = document.getElementById("buyBtn").addEventListener("click", buyCart);
function clearCart() {
removeCart = window.confirm("Are you sure you want to clear your cart?");
if (removeCart) {
inputCart.innerHTML = "";
document.getElementById('priceTotal').innerHTML = "Total: " + "0" + "$";;
console.clear();
}
};
function buyCart() {
shopMore = window.confirm("Do you want to checkout?");
if (shopMore) {
alert("Thank your for shopping at CatchShop!");
window.location.reload();
}
};
let cartIcon = document.querySelector("#cart-icon");
let cart = document.querySelector(".cart");
let closeCart = document.querySelector("#close-cart");
cartIcon.onclick = () => {
cart.classList.add("active");
};
closeCart.onclick = () => {
cart.classList.remove("active");
};
Thanks in advance! :)
You are calling the totalSum() function for every item. And sum always gets "reset" to 0. Try to add let sum = 0 before this line Object.keys(localStorage).forEach(function (key) {
function totalSum() {
cartPrice = document.querySelectorAll(".cart-price");
let sum = 0; // Everytime this function gets called, sum gets reset
for (let i = 0; i < cartPrice.length; i++) {
sum = sum + completeProduct[3];
console.log(sum);
document.getElementById('priceTotal').innerHTML = "Total: " + sum + "$";
}
}
Solution:
let inputCart = document.querySelector(".inputCart");
function returnKey(clickedId) {
cart.classList.add("active");
console.log(clickedId);
if (localStorage.length !== 0) {
let sum = 0;
Object.keys(localStorage).forEach(function (key) {
productObject = JSON.parse(localStorage.getItem(key));
completeProduct = productObject;
if (completeProduct[5] == clickedId) {
let cartPrice = document.createElement("p");
let cartTitle = document.createElement("p");
let cartImage = document.createElement("img");
inputCart.appendChild(cartPrice);
inputCart.appendChild(cartImage);
inputCart.appendChild(cartTitle);
cartPrice.setAttribute("class", "cart-price")
cartTitle.setAttribute("class", "cart-title");
cartImage.setAttribute("src", completeProduct[2]);
cartImage.setAttribute("width", "75");
cartImage.setAttribute("height", "75");
cartTitle.innerHTML = completeProduct[0];
cartPrice.innerHTML = "$" + completeProduct[3];
console.log(cartPrice);
console.log(cartTitle);
totalSum(sum);
}
})
}
};
function totalSum(sum) {
cartPrice = document.querySelectorAll(".cart-price");
for (let i = 0; i < cartPrice.length; i++) {
sum = sum + completeProduct[3];
console.log(sum);
document.getElementById('priceTotal').innerHTML = "Total: " + sum + "$";
}
}
Edit Solution:
You don't even need the totalSum function. You can simply increase the value of the sum variable with the current price of the local storage object you are iterating.
let inputCart = document.querySelector(".inputCart");
function returnKey(clickedId) {
cart.classList.add("active");
console.log(clickedId);
if (localStorage.length !== 0) {
let sum = 0;
Object.keys(localStorage).forEach(function (key) {
productObject = JSON.parse(localStorage.getItem(key));
completeProduct = productObject;
sum += completeProduct[3];
if (completeProduct[5] == clickedId) {
let cartPrice = document.createElement("p");
let cartTitle = document.createElement("p");
let cartImage = document.createElement("img");
inputCart.appendChild(cartPrice);
inputCart.appendChild(cartImage);
inputCart.appendChild(cartTitle);
cartPrice.setAttribute("class", "cart-price")
cartTitle.setAttribute("class", "cart-title");
cartImage.setAttribute("src", completeProduct[2]);
cartImage.setAttribute("width", "75");
cartImage.setAttribute("height", "75");
cartTitle.innerHTML = completeProduct[0];
cartPrice.innerHTML = "$" + completeProduct[3];
console.log(cartPrice);
console.log(cartTitle);
document.getElementById('priceTotal').innerHTML = "Total: " + sum + "$";
}
})
}
};
Edit:
function returnKey(clickedId) {
cart.classList.add("active");
console.log(clickedId);
if (localStorage.length !== 0) {
Object.keys(localStorage).forEach(function (key) {
productObject = JSON.parse(localStorage.getItem(key));
completeProduct = productObject;
if (completeProduct[5] == clickedId) {
let cartPrice = document.createElement("p");
let cartTitle = document.createElement("p");
let cartImage = document.createElement("img");
inputCart.appendChild(cartPrice);
inputCart.appendChild(cartImage);
inputCart.appendChild(cartTitle);
cartPrice.setAttribute("class", "cart-price")
cartTitle.setAttribute("class", "cart-title");
cartImage.setAttribute("src", completeProduct[2]);
cartImage.setAttribute("width", "75");
cartImage.setAttribute("height", "75");
cartTitle.innerHTML = completeProduct[0];
cartPrice.innerHTML = "$" + completeProduct[3];
console.log(cartPrice);
console.log(cartTitle);
let cartElements = document.getElementsByClassName('cart-price');
let sum = 0;
for (let i = 0; i < cartElements.length; ++i) {
let item = cartElements[i];
let price = item.innerText;
// Get rid of the $ sign and convert it to int
sum += parseInt(price.slice(1));
}
let inputCart = document.querySelector(".inputCart");
document.getElementById('priceTotal').innerHTML = "Total: " + sum + "$";
}
})
}
};
You are rewriting the innerHTML of the priceTotal element with the current sum value every cycle of the for loop.

problem using .split() method in a function but it works on console.log

the problem is only in the bottom function objectPutter
specifically the line with wowza.split(' '); labelled with the comment
let eq
let x = null
let bracketNum = 0
let k = 0
let pre = 0
class subEqCreator { //subEq object
constructor() {
this.precede = 0;
this.text = '';
}
parser() {
this.text += eq[k]
}
ma() {
this.text.split(' ')
}
};
function trigger() { //used for HTML onClick method
show();
brackets();
subEqDynamic()
parseEquation();
objectPutter()
};
function show() {
const recent = document.querySelector("ol");
const txt = document.getElementById('input');
eq = txt.value;
li = document.createElement('li');
li.innerText = eq;
recent.appendChild(li);
txt.value = '';
};
function brackets() { //counts how many brackets appear
for (let i = 0; i < eq.length; i++) {
if (eq[i] == "(") {
bracketNum++;
};
};
};
let subEqDynamic = function() { // creates a new object for each bracket
for (let count = 0; count <= bracketNum; count++) {
this['subEq' + count] = x = new subEqCreator()
};
};
function parseEquation() { // assign characters to SubEq object
let nextIndex = 0;
let currentIndex = 0;
let lastIndex = [0];
let eqLen = eq.length;
let nex = this['subEq' + nextIndex]
for (k; k < eqLen; k++) {
if (eq[k] == "(") {
nextIndex++;
pre++
this['subEq' + currentIndex].text += '( )';
this['subEq' + nextIndex].precede = pre;
lastIndex.push(currentIndex);
currentIndex = nextIndex;
} else if (eq[k] == ")") {
pre--
currentIndex = lastIndex.pop();
} else {
this['subEq' + currentIndex].parser()
}
}
}
function objectPutter() {
for (let i = 0; i < bracketNum; i++) {
let wowza = this['subEq' + i].text
wowza.split(' '); // 🚩 why isnt it working here
console.log(subEq0);
for (let j = 1; j <= wowza.length; j += 2) { // for loop generates only odds
let ni = i++
wowza.splice(j, 0, this['subEq' + ni])
console.log(i)
}
}
}
to fix this i tried;
making a method for it ma() in the constructor.
putting in the function parseEquation above in case it was a scope issue.
also, i noticed subEq0.split(' ') worked in browser console even replicating it to the way i done it using this['subEq' + i].text.split(' ') where i = 0.
After it runs the it says .splice is not a function and console.log(subEq0) shows subEq0.text is still a string
.split() does not change the variable it returns the splitted variable

Delete each letter before passing on the next array index?

I've been writing this little script in order to make a "self writing text" animation. The thing is, everytime a word is finished, it jumps straight up to the next one. I can't work my head around it to make so each letter deletes before passing on the next word in the array.
const text = ['design', 'make', 'develop', 'code', 'create']
let count = 0;
let index = 0;
let currentText = "";
let letter = "";
(function type() {
if (count === text.length) {
count = 0;
}
currentText = text[count];
letter = currentText.slice(0, ++index);
document.querySelector(".main__animation").textContent = letter;
if (letter.length === currentText.length) {
count++
index = 0;
}
setTimeout(type, 500);
}())
Thanks beforehand!
const text = [, 'design', 'make', 'develop', 'code', 'create']
let count = 1;
let index = 0;
let currentText = "";
let letter = "";
var deleting = false;
(function type() {
if (count === text.length) {
count = 1;
}
currentText = text[count];
if (deleting) {
letter = currentText.slice(0, --index);
} else {
letter = currentText.slice(0, ++index);
}
document.querySelector(".main-animation").textContent = letter;
if (letter.length === currentText.length) {
deleting = true;
}
if (letter.length === 0) {
count++;
index = 0;
deleting = false;
}
setTimeout(type, 500);
}())
<div class="main-animation"></div>
This is what I came along. Please let me know if there is any doubt.
function createAnimation(words, target, speed) {
let wordIndex = 0;
let i = 0;
let erase = false;
const handler = () => {
const word = words[wordIndex];
if (erase) {
target.innerText = target.innerText.slice(0, -1);
if (!target.innerText.length) {
wordIndex = (wordIndex + 1) % words.length;
i = 0;
erase = false;
}
} else {
target.innerText += word.charAt(i);
i++;
if (i === word.length) {
erase = true;
}
}
};
let interval = null;
return {
start() {
interval = setInterval(handler, 1000 / speed)
},
stop() {
clearInterval(interval);
}
}
}
const animation = createAnimation(
['design', 'make', 'develop', 'code', 'create'],
document.getElementById('target'),
5 // [letters/s]
);
animation.start();
// TO STOP, CALL animation.stop()
<h1 id='target'></h1>

how can I stop it from printing duplicate?

I'm I've created the below shopping cart, however, I'm trying to print the elements of the basket inside the HTML, but it's printing duplicated. If I add another element to the basket, it prints the new one + the previous one, even if the previous one is already printed.
Really appreciate the help, i'm new with JS, so struggling a bit.
thank you
//list of products
let products = [
{
name: "Logitech Pebble M350 Pink",
tag: "mouse",
price: 15.99,
count: ""
},
{
name: "Logitech K380 Multi-Device Bluetooth Keyboard",
tag: "keyboard",
price: 20.99,
count: ""
},
{
name: "Anker SoundCore min",
tag: "speaker",
price: 22.99,
count: ""
}
];
//ADD
function add() {
let quantity = document.getElementsByClassName("quantity");
let totalPrice = [];
let nameCount = 0;
let basket = [];
let basketSentence = document.getElementById("yourBasket");
for (let i = 0; i < quantity.length; i++) {
products[i].count = quantity[i].value;
}
for (var name in products) {
nameCount = nameCount + 1;
}
for (let i = 0; i < nameCount; i++) {
totalPrice[i] = products[i].price * products[i].count;
var sum = totalPrice.reduce(function(a, b) {
return a + b;
}, 0);
if (products[i].count != 0) {
basket.push(products[i]);
const newLi = document.createElement("li");
newLi.innerText = products[i].count + " " + products[i].name;
if (yourBasket.innerText != newLi.innerText) {
yourBasket.append(newLi);
console.log(yourBasket.innerText);
}
}
document.getElementById("total-value").innerHTML = sum;
}
}
//REMOVE
let removeBtn = document.querySelectorAll(".remove");
let quantity = document.querySelectorAll(".quantity");
removeBtn.forEach(function(check) {
check.addEventListener("click", checkIndex);
});
function checkIndex(event) {
let totalPrice = [];
let nameCount = 0;
var index = Array.from(removeBtn).indexOf(event.target);
console.log(index);
quantity[index].value = "";
products[index].count = quantity[index].value;
console.log(products[index].count);
for (var name in products) {
nameCount = nameCount + 1;
}
for (let i = 0; i < nameCount; i++) {
totalPrice[i] = products[i].price * products[i].count;
var sum = totalPrice.reduce(function(a, b) {
return a + b;
}, 0);
document.getElementById("total-value").innerHTML = sum;
}
}
Explanation
The solution is to reset the text of the basket before you start appending again.
Add the line yourBasket.innerHTML = ""; before the for loop.
The problem was that append was appending text. What you want is to actually clear the basket, and re-append all the totals of all the products.
Excerpt:
yourBasket.innerHTML = ""; //THIS IS THE LINE
for (let i = 0; i < nameCount; i++) {
totalPrice[i] = products[i].price * products[i].count;
Full code for Add:
//ADD
function add() {
let quantity = document.getElementsByClassName("quantity");
let totalPrice = [];
let nameCount = 0;
let basket = [];
let basketSentence = document.getElementById("yourBasket");
for (let i = 0; i < quantity.length; i++) {
products[i].count = quantity[i].value;
}
for (var name in products) {
nameCount = nameCount + 1;
}
yourBasket.innerHTML = ""; //(newLi);
for (let i = 0; i < nameCount; i++) {
totalPrice[i] = products[i].price * products[i].count;
var sum = totalPrice.reduce(function(a, b) {
return a + b;
}, 0);
if (products[i].count != 0) {
basket.push(products[i]);
const newLi = document.createElement("li");
newLi.innerText = products[i].count + " " + products[i].name;
if (yourBasket.innerText != newLi.innerText) {
yourBasket.append(newLi);
console.log(yourBasket.innerText);
}
}
document.getElementById("total-value").innerHTML = sum;
}
}
JSFiddle: https://jsfiddle.net/4zyd1sow/4/

How to pause the entire code for 1 second in javascript

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);

Categories

Resources