Dynamically create any number of sequentially firing functions - javascript

(JavaScript) I have a function that deals player cards in a nice sequential fashion: Player, Dealer, Player, Dealer. Below is that part of the function which sequentially moves cards to the viewport.
setTimeout(()=>{
player1.style.top = `70%`;
player1.style.left = `30%`;
player1.style.transform = `rotate(${Math.floor(Math.random() * rotationMax)}deg)`;
setTimeout(() => {
dealer1.style.top = `8%`;
dealer1.style.left = `30%`
dealer1.style.transform = `rotate(${Math.floor(Math.random() * rotationMax)+1}deg)`;
setTimeout(() => {
player2.style.top = `70%`;
player2.style.left = `50%`
player2.style.transform = `rotate(${Math.floor(Math.random() * rotationMax)}deg)`;
setTimeout(() => {
flippedCard.style.top = '8%';
flippedCard.style.left = '44%';
}, 200)}, 200)}, 100)}, 200)
You can see that this block works only with a set number of cards (in this case 4). I am not yet good enough in Javascript to create function that would dynamically generate any number of cards to be dealt.
Can someone point me in the right direction? Specific question: how do you dynamically generate tasks that run one after another.

Make an array of dealer cards and player cards, and figure out the differences in the left you want for each. Then iterate over the arrays, delaying with await to make the code flat and readable:
const delay200 = () => new Promise(res => setTimeout(res, 200);
const playerCards = [player1, player2];
const dealerCards = [dealer1, dealer2];
const playerLeftIncrement = 20; // eg: 30%, then 50%, then 70%; adjust as needed
const dealerLeftIncrement = 14; // eg: 30%, then 44%, then 58%; adjust as needed
const applyStyle = (card, left) => {
Object.assign(
card.style,
{
top: '70%',
left,
transform: `rotate(${Math.floor(Math.random() * rotationMax)}deg)`,
}
);
};
for (let i = 0; i < playerCards.length; i++) {
applyStyle(playerCards[i], `${30 + i * playerLeftIncrement}%`);
await delay200();
applyStyle(dealerCards[i], `${30 + i * dealerLeftIncrement}%`);
await delay200();
}

It would be useful to have a function that looks something like:
callFunctionsWithDelays(functions, delays)
That would avoid the nested look to your code, and make it easy to dynamically generate. I'd write this using async/await syntax:
async function callFunctionsWithDelays(functions, delays) {
for (i = 0; i < functions.length; i++) {
functions[i].call()
await new Promise(resolve, setTimeout(resolve, delays[i]))
}
}

Related

Passing parameters into ES6 closure (for multiple P5.js sketches)

I am trying to make a 'generic' P5.js sketch that I can tweak based on a passed-in parameter, with the intent being to be able to generate multiple sketches on a single page to show how different inputs work side-by-side.
Following the guide I see syntax like this (and I've extended it to fill in multiple divs:
const s = ( sketch ) => {
let x = 100;
let y = 100;
sketch.setup = () => {
sketch.createCanvas(500, 500);
console.log(idx);
};
sketch.draw = () => {
sketch.background(100);
sketch.fill(255);
sketch.rect(x,y,50,50);
sketch.text
};
};
let myp5_1 = new p5(s, document.getElementById('p5-sketch1'));
let myp5_2 = new p5(s, document.getElementById('p5-sketch2'));
let myp5_3 = new p5(s, document.getElementById('p5-sketch3'));
I am not great with ES6, but I'm struggling with passing a set of parameters in to be able to tweak the P5.js code.
What I would like to do is to pass in, say, an ID variable into each instance of s and have the sketch execute differently, rather than making three separate const s calls and duplicating data.
Create a function that takes idx and returns the original function.
const s = (idx) => ( sketch ) => {
let x = 100;
let y = 100;
sketch.setup = () => {
sketch.createCanvas(500, 500);
console.log(idx);
};
sketch.draw = () => {
sketch.background(100);
sketch.fill(255);
sketch.rect(x,y,50,50);
sketch.text
};
};
let myp5_1 = new p5(s(0), document.getElementById('p5-sketch1'));
let myp5_2 = new p5(s(1), document.getElementById('p5-sketch2'));
let myp5_3 = new p5(s(2), document.getElementById('p5-sketch3'))

loop through elements and set CSS styling at same time

So I want to loop through elements with a class and then loop the individual element and gradually decrease the "left" css property.
let move = document.getElementsByClassName("move");
for (var i = 0; i < move.length; i++) {
const left = move[i].getBoundingClientRect().left;
const elementId = move[i].id;
for (let j = left; j > -20; j--) {
document.getElementById(elementId).style.left = j + "%";
await new Promise(resolve => setTimeout(resolve, 20));
}
}
However, I'm using "await" to delay so that it moves slowly and doesn't zip across the screen. I want it so that all the elements have their CSS left property decrease at the same time. But instead, because of the await, the first element increases its left property and when its left property reaches the end, only then the next element goes. Please advise
While I think it would be better to use pure CSS transitions/animations for this, you could use a while loop to iterate over the elements with the move class, subtracting 1% from their left value until they are completely off screen (right <= 0).
const start = async () => {
const move = document.getElementsByClassName("move");
const canMoveLeft = () => {
const move = document.getElementsByClassName("move");
for (let i = 0; i < move.length; i++) {
if (move[i].getBoundingClientRect().right > 0) return true;
}
return false;
};
while (canMoveLeft()) {
for (let i = 0; i < move.length; i++) {
const { left, right } = move[i].getBoundingClientRect();
if (right > 0) {
const windowWidth = window.innerWidth;
const leftVal = `${Math.round((left / windowWidth) * 100) - 1}%`;
document.getElementById(move[i].id).style.left = leftVal;
}
}
await new Promise((resolve) => setTimeout(resolve, 20));
}
};
From what I understood, you want to animate left property of elements with class "move".
If that's the case you can do:
1st:
add transition property in move class
.move {
transition: left 2s;
}
2nd:
let move = document.getElementsByClassName("move");
for(let item of move) {
item.style.left = "-20%";
}

Running an animated loop in PIXI.JS

I am trying to create a bottle pouring animation that loops through 5 different bottles, each bottle is sideways and has a different amount of liquid pouring out. I am trying to display the first bottle and then the second after 60ms, then the 3rd after 60ms, and so on and so on. I need to remove the previous bottle and add the next bottle in the exact same place. I am wondering what the most concise way to do with would be, I have tried with several setTimout functions but the code has some bugs and is not concise at all. I have researched PIXI.Timer but am struggling to understand how to set 5 different sprites and loop through them. Let me know if you have any ideas or direction. I will post my function with setTimout used below:
setTimeout(() => {
let pour1Texture = new PIXI.Texture.from(require('#/assets/items/bottle/pouring/pouring bottle1.png'))
let pour1 = new PIXI.Sprite.from(pour1Texture)
sprites.push(pour1)
pour1.position.x = 438;
pour1.position.y = -40;
labBenchComponent.pixiApp.stage.addChild(
pour1
);
},1000)
setTimeout(() => {
labBenchComponent.pixiApp.stage.removeChild(sprites.pop())
const pour2Texture = new PIXI.Texture.from(require('#/assets/items/bottle/pouring/pouring bottle2.png'))
const pour2 = new PIXI.Sprite.from(pour2Texture)
pour2.position.x = 438;
pour2.position.y = -10;
sprites.push(pour2)
labBenchComponent.pixiApp.stage.addChild(
pour2
);
}, 1000)
setTimeout(() => {
labBenchComponent.pixiApp.stage.removeChild(sprites.pop())
const pour3Texture = new PIXI.Texture.from(require('#/assets/items/bottle/pouring/pouring bottle2.png'))
const pour3 = new PIXI.Sprite.from(pour3Texture)
pour3.position.x = 438;
pour3.position.y = 10;
sprites.push(pour3)
labBenchComponent.pixiApp.stage.addChild(
pour3
);
}, 1000)
I figured it out. Instead of using a tick use PIXI.AnimatedSprite like the following:
import * as PIXI from 'pixi.js-legacy';
export default function pourBottle() {
let textureArray = [];
for (let i = 0; i < 5; i++)
{
let texture = {
texture: PIXI.Texture.from(require(`#/assets/items/bottle/pouring/pouring bottle${i+1}.png`)),
time: 100,
};
textureArray.push(texture);
};
let animatedSprite = new PIXI.AnimatedSprite(textureArray);
return animatedSprite;
}

Javascript - Merge Sort Visualizer using CSS Style to Sort, having issues

I am having an issue with my merge sort visualizer.
My program has no issues visualizing bubble sort or quick sort, as I can do the swapping operation of css property values in-place, but I am having major issues trying to get merge sort to work properly. The issue arises when I try to update a css property on the dom, it causes the sort to not function.
I have tried passing in copies of the data I wish to sort, and all sorts of weird things I could think of to make it work. I am currently trying to sort by the css property 'maxWidth'. I use that to display how large a div element is in the html file and then visualize the sort from there.
My latest thought has been to set all the div elements to have another css property equal to the maxWidth (I am using fontSize as it does not affect my program) and then sorting based on fontSize, allowing me in theory to change the maxWidth properties of the divs without affecting merge sorts algorithm.
I am including my entire js file as I hope reading my correctly working bubble sort or quick sort functions can help you see what I am trying to achieve. Thank you so much for taking the time to read this and offer any help!
Important Note: I am not trying to visualize the individual steps of merge sort yet because I am unable to update the final result to the html page without affecting the merge sort algorithm. According to console logs, my merge sort algorithm does indeed work, I just can't update the DOM without messing it up. Once I can do that, I will turn it into an asynchronous function using async and await like I previously did with bubble and quick sort.
/********* Generate and Store Divs to be Sorted *************/
const generateSortingDivs = (numOfDivs) => {
const divContainer = document.querySelector('.div-container');
let html = '';
for (let i = 0; i < numOfDivs; i++) {
let r = Math.floor(Math.random() * 100);
html += `<div class='sorting-div' id='id-${i}' style='max-width: ${r}%'>&nbsp</div>`;
}
divContainer.innerHTML = html;
for(let i = 0; i < numOfDivs; i++) {
let x = document.getElementById('id-' + i);
x.style.fontSize = x.style.maxWidth;
}
}
const storeSortingDivs = () => {
const divContainer = document.querySelector('.div-container');
let divCollection = [];
const numOfDivs = divContainer.childElementCount;
for(let i=0; i<numOfDivs; i++) {
let div = document.getElementById('id-' + i);
divCollection.push(div);
}
return divCollection;
}
/********** SLEEP FUNCTION ************/
//Used to allow asynchronous visualizations of synchronous tasks
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/******* SWAP FUNCTIONS *********/
//Used for Testing Algorithm before Animating Visualization
const syncSwap = (div1, div2) => {
let tmp = div1.style.maxWidth;
div1.style.maxWidth = div2.style.maxWidth;
div2.style.maxWidth = tmp;
}
async function asyncSwap(div1, div2) {
await sleep(50);
let tmp = div1.style.maxWidth;
div1.style.maxWidth = div2.style.maxWidth;
div2.style.maxWidth = tmp;
}
const swapDivs = (smallerDiv, biggerDiv) => {
return new Promise(resolve => {
setTimeout(() => {
let tmp = smallerDiv.style.maxWidth;
smallerDiv.style.maxWidth = biggerDiv.style.maxWidth;
biggerDiv.style.maxWidth = tmp;
resolve();
}, 50);
});
}
/****************************************/
/*********** SORTING ALGO'S *************/
/****************************************/
/******* BUBBLE SORT ***********/
async function bubbleSort(divCollection) {
displayBubbleSortInfo();
const len = divCollection.length;
for(let i=0; i<len; i++) {
for(let j=0; j<len-i-1; j++) {
divCollection[j].style.backgroundColor = "#FF4949";
divCollection[j+1].style.backgroundColor = "#FF4949";
let numDiv1 = parseInt(divCollection[j].style.maxWidth);
let numDiv2 = parseInt(divCollection[j+1].style.maxWidth);
let div1 = divCollection[j];
let div2 = divCollection[j+1];
if(numDiv1 > numDiv2) {
await swapDivs(div2, div1);
}
divCollection[j].style.backgroundColor = "darkcyan";
divCollection[j+1].style.backgroundColor = "darkcyan";
}
divCollection[len - i - 1].style.backgroundColor = 'black';
}
}
function displayBubbleSortInfo(){
const infoDiv = document.querySelector('.algo-info');
let html = `<h1>Bubble Sort Visualizer</h1>`;
html += `<h2>Time Complexity: O(n^2)</h2>`;
html += `<h3>Space Complexity: O(1)</h3>`;
html += `<p>This sorting algorithm loops through the array and continues to push the
largest found element into the last position, also pushing the last available
position down by one on each iteration. It is guaranteed to run in exactly
O(n^2) time because it is a nested loop that runs completely through.</p>`;
infoDiv.innerHTML = html;
}
/****** QUICK SORT ********/
async function quickSort(divCollection, start, end) {
if(start >= end) return;
let partitionIndex = await partition(divCollection, start, end);
await Promise.all([quickSort(divCollection, start, partitionIndex - 1), quickSort(divCollection, partitionIndex + 1, end)]);
}
/* This function takes last element as pivot, places
the pivot element at its correct position in sorted
array, and places all smaller (smaller than pivot)
to left of pivot and all greater elements to right
of pivot */
async function partition(divCollection, start, end) {
let pivotIndex = start;
let pivotValue = parseInt(divCollection[end].style.maxWidth);
for(let i = start; i < end; i++) {
if(parseInt(divCollection[i].style.maxWidth) < pivotValue) {
await asyncSwap(divCollection[i], divCollection[pivotIndex]);
pivotIndex++;
}
}
await asyncSwap(divCollection[pivotIndex], divCollection[end]);
return pivotIndex;
}
function displayQuickSortInfo(){
const infoDiv = document.querySelector('.algo-info');
let html = `<h1>Quick Sort Visualizer</h1>`;
html += `<h2>Time Complexity: O(n log n)</h2>`;
html += `<h3>Space Complexity: O(log n)</h3>`;
html += `<p>This sorting algorithm uses the idea of a partition to sort
each iteration recursively. You can implement quick sort
in a variety of manners based on the method in which you
pick your "pivot" value to partition the array. In this
visualization, I implemented the method that chooses the
last element of the array as the pivot value. You could
also choose the first value, the middle value, or the median
value based on the first, middle, and last values.</p>`;
infoDiv.innerHTML = html;
}
/* Merge Sort does not sort in place, and thus we have to be
* clever when implementing it and also editing the css style
* of our divs to show the visualization of how the algorithm
* works. My method is to store a copy of the divs, that way
* I can use one to be sorted by merge sort, and the other to
* change the css style property to show the visualization.
* Unlike Quick Sort and Bubble Sort, we are not swapping
* elements when sorting, instead we are merging entire
* arrays together as the name implies. */
function mergeSort(divCollection) {
if(divCollection.length < 2) return divCollection;
let middleIndex = Math.floor(divCollection.length / 2);
let left = divCollection.slice(0, middleIndex);
let right = divCollection.slice(middleIndex);
return merge(mergeSort(left), mergeSort(right));
}
function merge(left, right) {
let mergedCollection = [];
while(left.length && right.length) {
if(parseInt(left[0].style.fontSize) < parseInt(right[0].style.fontSize || right.length === 0)) {
let el = left.shift();
mergedCollection.push(el);
} else {
let el = right.shift();
mergedCollection.push(el);
}
}
let res = mergedCollection.concat(left.slice().concat(right.slice()));
return res;
}
/***** INITIALIZATION FUNCTION *******/
generateSortingDivs(10);
let divs = storeSortingDivs();
let copyDivs = [...divs];
console.log('Original State: ')
console.log(divs);
//bubbleSort(divs);
//displayQuickSortInfo();
//quickSort(divs, 0, divs.length-1);
let x = mergeSort(copyDivs);
console.log('Sorted: ');
console.log(x);

Functional Programming: Calling a Curried Function

I'm implementing the game Tic Tac Toe/Naughts and Crosses in a functional programming style and have stumbled across a hurdle with curried functions.
I have a reoccurring pattern of functions in the form func(width, height, index) which I then wish to curry, binding width and height and leaving curriedFunc(index).
However the problem arises when I have functions that expect one of these curried functions to be defined at compile-time.
They cannot be defined at compile time, because they need input from the user to then bind the values to the function.
Below is some example code of the pattern I've encountered.
// Board indexes:
// 0 | 1 | 2
// ---+---+---
// 3 | 4 | 5
// ---+---+---
// 6 | 7 | 8
const getRowNumGivenWidth = w => i => Math.floor(i/w);
// I want to be able to declare nextIndexInRowGivenWidth() here, outside of main()
// but getRowNum() needs to be defined beforehand
const main = () => {
// User input:
const width = 3;
// ...
const getRowNum = getRowNumGivenWidth(width);
const nextIndexInRowGivenWidth = width => currentIndex => {
const rowNum = getRowNum(currentIndex);
const nextIndex = currentIndex + 1;
if (getRowNum(nextIndex) != rowNum)
result = nextIndex - width;
else
result = nextIndex;
return result;
};
const nextIndexInRow = nextIndexInRowGivenWidth(width);
const board = [0, 1, 2, 3, 4, 5, 6, 7, 8];
board.map(x => console.log(x, " -> ", nextIndexInRow(x)));
// ...
}
main();
The only way I can think of solving this is to pass the curried function as an argument (to nextIndexInRowGivenWidth() in this example).
However I don't think this is ideal as if a function requires a few similarly curried functions at run-time, it quickly becomes unwieldy to define and curry said function.
The ideal solution would be if I could somehow make the binding of the values dynamic, suppose I could put the declaration getRowNum = getRowNumGivenWidth(width); before main(). This way I could call something like getRowNum(someInt) to initialise getRowNum() which I could then use in other functions that are already expecting it to be defined.
As this is a reoccurring pattern in my code, I was wondering if there is a design pattern to achieve this.
I think you are looking for
const getRowNumGivenWidth = w => i => Math.floor(i/w);
const nextIndexInRowGivenWidth = width => {
const getRowNum = getRowNumGivenWidth(width);
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
return currentIndex => {
const nextIndex = currentIndex + 1;
if (getRowNum(nextIndex) != getRowNum(currentIndex))
return nextIndex - width;
else
return nextIndex;
};
};
const main = () => {
// User input:
const width = 3;
const nextIndexInRow = nextIndexInRowGivenWidth(width);
// ...
}
Alternatively, you could define that nextIndexInRowGiven… function not with the width as the first curried parameter, but with getRowNum itself as the parameter:
const getRowNumGivenWidth = w => i => Math.floor(i/w);
const nextIndexInRowGivenRowNumGetter = getRowNum => currentIndex => {
const nextIndex = currentIndex + 1;
if (getRowNum(nextIndex) != getRowNum(currentIndex))
return nextIndex - width;
else
return nextIndex;
};
const main = () => {
// User input:
const width = 3;
const nextIndexInRow = nextIndexInRowGivenRowNumGetter(getRowNumGivenWidth(width));
// ...
}

Categories

Resources