Shuffle an array of cards on click of a button - javascript

I want to be able to click on the button below and shuffle the cards in my card deck.
So far I have created an event listener which when clicked calls the function shuffleCards which takes an array. The array I have passed in is the one provided to me in the exercise which is an array of cards.
Nothing happens when I click on the button, I know it is something to do with scope but I do not know how to amend my code to make this work properly.
Thank you in advance.
HTML
<button type="button" id="shuffle" class="shuffle" class="btn btn-lg btn-secondary">Shuffle</button>
JAVASCRIPT
// Part given in exercise which creates a deck of cards:
const suit = 'hearts';
const cardsWrapper = document.querySelector('.cards-wrapper');
function createCards() {
const cards = [];
// Create an array with objects containing the value and the suit of each card
for (let i = 1; i <= 13; i += 1) {
const cardObject = {
value: i,
suit,
};
cards.push(cardObject);
}
// For each dataObject, create a new card and append it to the DOM
cards.forEach((card, i) => {
const positionFromLeft = i * 15;
const cardElement = document.createElement('div');
cardElement.setAttribute('data-value', card.value);
cardElement.classList.add('card', `${card.suit}-${card.value}`);
cardElement.style.left = `${positionFromLeft}px`;
cardsWrapper.append(cardElement);
});
}
// part written by me
const shufflebtn = document.getElementById('shuffle');
shufflebtn.addEventListener("click", () => {
shuffleCards(cards);
})
function shuffleCards(array) {
var i = 0
, j = 0
, temp = null
for (i = array.length - 1; i > 0; i -= 1) {
j = Math.floor(Math.random() * (i + 1))
temp = array[i]
array[i] = array[j]
array[j] = temp
}
return array
}

made some changes to code where i am not talking about html instead giving a solution for your card shuffle.
created a create cards button which triggers createCards function then, try clicking your shuffle and see console log you can see a shuffled array.
Note: click on create cards Button before you click on shuffle as you might log empty array
Also i changed the scope of cards as it should be accessible to other functions
const suit = 'hearts';
var cards = [];
function createCards() {
for (let i = 1; i <= 13; i += 1) {
const cardObject = {
value: i,
suit,
};
cards.push(cardObject);
}
}
const shufflebtn = document.getElementById('shuffle');
const creatorBtn = document.getElementById('creator');
shufflebtn.addEventListener("click", () => {
cards = shuffleCards(cards);
})
creatorBtn.addEventListener("click", () => {
createCards();
})
function shuffleCards(array) {
var i = 0,
j = 0,
temp = null
for (i = array.length - 1; i > 0; i -= 1) {
j = Math.floor(Math.random() * (i + 1))
temp = array[i]
array[i] = array[j]
array[j] = temp
}
console.log(array)
return array;
}
<button type="button" id="shuffle" class="shuffle" class="btn btn-lg btn-secondary">Shuffle</button>
<button type="button" id="creator" class="creator" class="btn btn-lg btn-secondary">create cards</button>

Related

Pick random index and change till all unique are done then restart

I have image gallery with 6 image slot and i have a array with n no of image object like this
"src" : {
"1x" : "/clients/Logo-1.png",
"2x" : "/clients/Logo-1#2x.png",
"3x" : "/clients/tLogo-1#3x.png"
},
"alt" : "xyz"
}
what i want is to show random 6 image from array and then every 5 sec randomly one slot need to be change and get update with a new unique image which must not be in first 6 slot and then after finishing all it should continue the 5 sec change with a new unique image which must not be in those 6 slot.
what i have tried
let randomList = this.shuffleArray(this.LogosListObj);
let FirstSixImg = randomList.slice(0, 6);
let LeftAllImg = randomList.slice(6 + 1);
let RandomIndex = this.randomNoRepeats([0,1,2,3,4,5])
let RandomSecoundImg = this.randomNoRepeats(Array.apply(null, new Array(LeftAllImg.length)).map(function(el, i) {return i}))
let RandomFirstImg = this.randomNoRepeats(Array.apply(null, new Array(FirstSixImg.length)).map(function(el, i) {return i}))
this.ImageToShowList = [...FirstSixImg];
let checkArr = [];
let checkArr2 = [];
let flag = false;
let index,secndIndex,thirdIndex;
const LogoChange = (arr) =>{
if(!flag) {
secndIndex = RandomSecoundImg();
console.log('1st',secndIndex)
if(checkArr.indexOf(secndIndex) == -1) {
index = RandomIndex();
checkArr.push(secndIndex)
ctl.ImageToShowList[index] = {};
ctl.ImageToShowList[index] = LeftAllImg[secndIndex];
Vue.set(ctl.ImageToShowList, index, LeftAllImg[secndIndex])
ctl.PreviousImgObj = {...LeftAllImg[secndIndex]};
} else {
flag = true;
checkArr = [];
}
}
if(flag) {
thirdIndex = RandomFirstImg();
console.log('2nd',thirdIndex)
if(checkArr2.indexOf(thirdIndex) == -1) {
checkArr2.push(thirdIndex)
ctl.ImageToShowList[thirdIndex] = {};
ctl.ImageToShowList[thirdIndex] = FirstSixImg[thirdIndex];
Vue.set(ctl.ImageToShowList, thirdIndex, FirstSixImg[thirdIndex])
ctl.PreviousImgObj = {...FirstSixImg[thirdIndex]};
}else {
flag = false;
checkArr2 = [];
LogoChange();
}
}
}
setInterval(()=>{
LogoChange();
}, 1000);
where randomNoRepeats is
randomNoRepeats : (array) => {
var copy = array.slice(0);
return function() {
if (copy.length < 1) { copy = array.slice(0); }
var index = Math.floor(Math.random() * copy.length);
var item = copy[index];
copy.splice(index, 1);
return item;
};
and shuffleArray is
shuffleArray : (array) => {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
const temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
},
this.ImageToShowList is used in html part to display
Any help with logic or change will be appreciate.
I have my example fiddle here, but this is the breakdown:
Your data would look something like this:
let galleryPool = [
{
"src" : {
"1x" : "/clients/Logo-1.png",
"2x" : "/clients/Logo-1#2x.png",
"3x" : "/clients/tLogo-1#3x.png"
},
"alt" : "xyz",
"color": "red"
},
...
]
(I added a color property so you could see the changes since I don't actually have any images.)
The first thing I do is drop in my handy-dandy Fisher–Yates shuffle, since this is a great way to get a random element out of an array.
Array.prototype.shuffle = function() {
let currentIndex = this.length, randomIndex;
while (currentIndex != 0) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
[this[currentIndex], this[randomIndex]] = [this[randomIndex], this[currentIndex]];
}
return this;
}
I initialize the gallery first, and set up the 5-second timeout to change images in the gallery.
let displayedGallery = []
initialGallerySetup();
function initialGallerySetup() {
let galleryContainer = document.getElementById("gallery-container")
galleryPool.shuffle()
displayedGallery = galleryPool.splice(0, 6)
for(let index = 0; index < displayedGallery.length; index++) {
let data = displayedGallery[index]
galleryContainer.appendChild(generateGalleryItem(data))
}
galleryTickUpdate()
}
this function makes an img dom element I can then add to the container. I'm using your src here but you can change whatever values in here to alter how all of the images in the gallery are displayed.
function generateGalleryItem(data) {
let item = document.createElement("img")
item.style.backgroundColor = data.color
item.src = data.src[0]
item.className = "gallery-item"
return item
}
This function calls itself every 5 seconds, and will pick a random item from the gallery to swap with another item not currently displayed.
function galleryTickUpdate() {
setTimeout(() => {
let randomIndex = Math.floor(displayedGallery.length * Math.random())
swapItemAtIndex(randomIndex)
galleryTickUpdate()
},5000)
}
Here's the magic. I grab the randomly chosen item out of the displayed gallery items, pick a new item from the unused gallery pool items, put the old one back in the pool and the new one gets pushed back into the gallery display in the same spot.
function swapItemAtIndex(index) {
let galleryContainer = document.getElementById("gallery-container")
let displaySlot = galleryContainer.children[index]
let returning = displayedGallery[index]
galleryPool.shuffle()
let newDisplay = galleryPool.pop();
displayedGallery[index] = newDisplay
galleryPool.push(returning)
galleryContainer.insertBefore(generateGalleryItem(newDisplay), displaySlot)
galleryContainer.removeChild(displaySlot)
}
If you want to enforce running through the whole array, just check when the galleryPool array is empty, then repopulate it and re-run the init function. Otherwise, this will happily run forever.
here is a simple script. you will need the following
pool this will contain all possible values you would like
single function to update the show array
Test Here
I've tried to put as much comments as possible
// Pool with all possible images (you may use any type of text as long as each value is unique)
const pool = [
"1.png",
"2.png",
"3.png",
"4.png",
"5.png",
"6.png",
"7.png",
"8.png",
"9.png",
"10.png",
];
// this will store the "6" unique elements which you will be displaying
let show = [];
// get the first 6 random but unique elements. we will monitor the length of the `show` array
while(show.length < 6){
// randomly sort the pool and get the first element of the sorted array and store it into a `pooled` variable.
let pooled = pool.sort(() => 0.5 - Math.random())[0];
// check if the pooled value exists in the `show` array if it doesnt then add it and repeat the loop till all 6 slots are filled with unique values
if(!show.includes(pooled)){
// add `pooled` item to the `show` array
show.push(pooled);
}
}
// do the same as the above with a slight change, of only replacing one till all are unique.
function after5Mins(){
// get a new random item from the pool
let newPoolItem = pool.sort(() => 0.5 - Math.random())[0];
// using a while loop check if the new `pool item` is in the show array, if not then skip and add it
while(show.includes(newPoolItem)){
newPoolItem = pool.sort(() => 0.5 - Math.random())[0]; // set the pool item to a random one, then loop and check, follow previous comment
}
// once a new un used item is found, then assign it to a random position
show[Math.floor(Math.random()*show.length)] = newPoolItem;
}
// call the after 5 mintes using
setTimeout(()=>{
after5Mins();
}, 300000);

JavaScript not updating the DOM and crashing the browser

I have written the following HTML for a button that on click calls a function and the output of the function is written in a div in the DOM. But it is not updating the DOM anyway, but freezing the whole browser tab as well as the HTML page. Please help.
Thank you in advance
let rows = [];
let cols = [];
let secKey = "";
const generateKey = () => {
var count = 0;
while (count != 5) {
let randomNumber = Math.floor((Math.random() * 10));
if (!rows.includes(randomNumber)) {
rows.push(randomNumber);
count++;
}
}
count = 0;
while (count != 6) {
let randomNumber = Math.floor((Math.random() * 10));
if (!cols.includes(randomNumber)) {
cols.push(randomNumber);
count++;
}
}
// put on the document
secKey = `${cols[0]}${rows[0]}${cols[1]}${rows[1]}${cols[2]}${rows[2]}${cols[3]}${rows[3]}${cols[4]}${rows[4]}${cols[5]}`;
document.querySelector("#sec-key").innerHTML = `Your secret key is <strong id="sec-key">${secKey}</strong>`; // #sec-key is a div where I want to show the output
};
Html:
<div class="key-container">
<button id="generate-key" class="util-btn" onclick="generateKey()">Generate new secret key</button>
<p class="key-holder" id="sec-key">
<!--output is expected here-->
</p>
<p id="caution">*please remember the secret key for decryption</p>
</div>
The problem is that upon running the function a second time, the globals likely already include the random values, and so the count variable is never incremented and the loop spins infinitely.
Either initialize the globals inside the function implementation, or use the length of the array instead of a counter. Second approach is shown below:
let rows = [];
let cols = [];
let secKey = "";
const generateKey = () => {
while (rows.length != 5) {
let randomNumber = Math.floor((Math.random() * 10));
if (!rows.includes(randomNumber)) {
rows.push(randomNumber);
}
}
while (cols.length != 6) {
let randomNumber = Math.floor((Math.random() * 10));
if (!cols.includes(randomNumber)) {
cols.push(randomNumber);
}
}
// put on the document
secKey = `${cols[0]}${rows[0]}${cols[1]}${rows[1]}${cols[2]}${rows[2]}${cols[3]}${rows[3]}${cols[4]}${rows[4]}${cols[5]}`;
document.querySelector("#sec-key").innerHTML = `Your secret key is <strong id="sec-key">${secKey}</strong>`; // #sec-key is a div where I want to show the output
};

Get a total value from an array containing both strings ('A','J'...) and numbers (deck of cards in an array)

I have an issue where I have an array containing a deck of cards (['A', 2,3,...'J',...])
I want to be able to pick a number of random cards and then get the total sum of them. for example J,4 should give me the total value of 14.
my current problem is that I can't figure out how to change the strings in the array to a number and
then add those together to get the total sum.
my current code is:
blackjackGame={
'you': 0,
'cards': ['A','2','3','4','5','6','7','8','9','10','J','Q','K'],
'cardsMap' : {'A':1, '2':2, '3':3, '4':4, '5':5, '6':6, '7':7, '8':8, '9':9, '10':10, 'J':10, 'Q':10, 'K':10},
}
let playerCards = 2
let card = [];
const YOU = blackjackGame['you']
// gives me a random card
function randomCard (){
let rand = Math.floor(Math.random()* 13)
return blackjackGame['cards'][rand];
}
// gives me the two starting cards for the player in an array so I can later add more
function start(){
for(let i= 0; i < playerCards; i++){
card.push(randomCard())
}
return card
}
function totalValue (player){
// this is where i have no idea what to do
// let player = card.reduce(function (a,b){
// return a +b
// }, 0)
// return player += blackjackGame['cardsMap'][card[0]]
}
console.log(start())
console.log(showScore(YOU)) ```
PS. I'm trying to create a blackjack game.
Your reduce code is fine. Just add the reference to blackjackGame.cardsMap to retrieve the value that corresponds to card b.
let sum = card.reduce(function(a, b) {
return a + blackjackGame.cardsMap[b];
}, 0);
Note that you cannot return that value via the argument of the function. Instead let the function return it with a return statement:
return sum;
const blackjackGame={
'you': 0,
'cards': ['A','2','3','4','5','6','7','8','9','10','J','Q','K']
}
let playerCards = 2
let card = [];
const YOU = blackjackGame['you']
function getCardValue(card) {
const v = blackjackGame['cards']
if(v.indexOf(card) === -1){
throw Error("not found")
}
// for each card above index 9 (10), always return 10
return v.indexOf(card) > 9 ? 10 : v.indexOf(card) + 1
}
function randomCard (){
let rand = Math.floor(Math.random()* 13)
return blackjackGame['cards'][rand];
}
function deal(){
for(let i= 0; i < playerCards; i++){
card.push(randomCard())
}
return card
}
function calculateValue (cards){
return cards.reduce(function (total, num){
return total + getCardValue(num)
}, 0)
}
document.getElementById('deal').addEventListener('click',(e) => {
const cards = deal()
console.log(cards)
const playerValue = calculateValue(cards)
YOU = playerValue
console.log(playerValue)
})
<html>
<head>
</head>
<body>
<button id="deal">Deal</button>
<span id=cards />
<span id=total />
</body>
</html>
You need a way to map the face to the value. This will work:
function getValueOfCard( face ) {
var cardOrder =" A234567891JQK";
var faceStart = (""+face).substring(0,1);
return Math.min(10, cardOrder.indexOf(faceStart))
}
If you want to get the values of all your cards, simply iterate over them (faster than reduce, and more easy to read).
Your card only needs the face and color, the other values follow.
card = { color: "spades", face : "King" };
getValueOfCard( card.face );
function totalValue ( playerHand ){
// assuming an array of cards is the player hand
var total = 0;
for ( var card in playerHand ) {
total += getValueOfCard( card.face );
}
return total;
}
I also recommend, that you create all your cards in one go, and then shuffle them, by picking two random numbers and switching these two cards. Do this in a loop for a couple of times, and you have a randomized stack of cards, which means you can actually treat it as a stack.
cardColors = ["♠","♥","♦","♣"];
cardFaces = ['A','2','3','4','5','6','7','8','9','10','J','Q','K'];
// create deck of cards
var stackOfCards = [];
for ( var a = 0; a < cardColors.length; a++ ) {
var curColor = cardColors[a];
for ( var i = 0; i < cardFaces.length; i++) {
var curFace = cardFaces[i];
card = { color : curColor, face : curFace };
stackOfCards.push(card);
}
}
// shuffle the deck
// then you can pop cards from the stack to deal...
function start () {
for (let i = 0; i < playerCards; i++) {
cards.push(randomCard())
}
totalValue(cards)
}
function totalValue (cards) {
cards.forEach((card) => {
blackjackGame.you += blackjackGame.cardsMap[card]
})
}
start()
console.log(blackjackGame.you)
You were on the right track with having a map. You can access objects with a variable by using someObj[yourVar]

States not updating

I'm coding a sorting visualizer in ReactJS, and I use a state to hold the delay between each render.
When I change the slider of the delay, the sorting does not update.
I made it log the updated value, and in each loop I made it log the value it reads.
for some reason, when I read the getDelay inside the loop, and outside of it, they are different.
Here is the code:
import React, { useState, useEffect } from "react";
import "./SortingVisualizer.css";
class Bar {
constructor(value, className) {
this.value = value;
this.className = className;
}
}
const SortingVisualizer = () => {
const [getArray, setArray] = useState([Bar]); //array to hold the bars
const [getSlider, setSlider] = useState(50);
const [getDelay, setDelay] = useState(2);
//reset the array at the start
useEffect(() => {
resetArray(10);
}, []);
//function to reset the array
const resetArray = () => {
const array = [];
for (let i = 0; i < getSlider; i++) {
array.push(new Bar(randomInt(20, 800), "array-bar"));
}
setArray(array);
};
//a delay function. use like this: `await timer(time to wait)`
const timer = delay => {
return new Promise(resolve => setTimeout(resolve, delay));
};
//function to do buuble sort with given delay between each comparison
const bubbleSort = async () => {
let temp,
array = Object.assign([], getArray); // defining a temporary variable, and a duplicate array the the bars array
//looping from the array size to zero, in cycles
for (let i = array.length; i > 0; i--) {
//looping from the start of the section from the first loop to the end of it.
for (let j = 0; j < i - 1; j++) {
//changing the colors of the compared bares
array[j].className = "array-bar compared-bar";
array[j + 1].className = "array-bar compared-bar";
if (getDelay > 0) await timer(getDelay / 2);
setArray([...array]);
//comparing and switching if needed
if (array[j].value > array[j + 1].value) {
temp = array[j].value;
array[j].value = array[j + 1].value;
array[j + 1].value = temp;
setArray([...array]);
}
//updating the array and moving to the next pair
if (getDelay > 0) await timer(getDelay / 2);
array[j].className = "array-bar";
array[j + 1].className = "array-bar";
// Wait delay amount in ms before continuing, give browser time to render last update
}
array[i - 1].className = "array-bar completed-bar";
}
setArray([...array]);
console.log("done.");
};
const combSort = async () => {
let temp,
swapped,
array = Object.assign([], getArray); // defining a temporary variable, and a duplicate array the the bars array
//looping from the array size to zero, in cycles
for (let i = array.length; i > 0; i = Math.floor(i / 1.3)) {
//looping from the start of the section from the first loop to the end of it.
swapped = false;
for (let j = 0; j < array.length - i; j++) {
//changing the colors of the compared bares
array[j].className = "array-bar compared-bar";
array[j + i].className = "array-bar compared-bar";
setArray([...array]);
await timer(getDelay / 2);
//comparing and switching if needed
if (array[j].value > array[j + i].value) {
temp = array[j].value;
array[j].value = array[j + i].value;
array[j + i].value = temp;
setArray([...array]);
swapped = true;
await timer(getDelay / 2);
}
//updating the array and moving to the next pair
array[j].className = "array-bar";
array[j + i].className = "array-bar";
// Wait delay amount in ms before continuing, give browser time to render last update
console.log(getDelay);
}
//array[i - 1].className = "array-bar completed-bar";
if (i === 1 && swapped) i = 2;
}
setArray([...array]);
};
const sliderUpdate = e => {
setSlider(e.target.value);
resetArray(getSlider);
};
const delayUpdate = e => {
setDelay(e.target.value * 1);
console.log(getDelay);
};
return (
<>
<div className="menu">
<button onClick={() => resetArray()}>Geneate new array</button>
<button onClick={() => bubbleSort()}>Do bubble sort</button>
<button onClick={() => combSort()}>Do comb sort</button>
</div>
<div class="slide-container">
<input
type="range"
min="3"
max="250"
value={getSlider}
class="slider"
id="sizeSlider"
onChange={sliderUpdate}
/>
<input
type="range"
min="0"
max="1000"
value={getDelay}
class="slider"
id="delaySlider"
onChange={delayUpdate}
/>
</div>
<div className="array-container">
{getArray.map((bar, i) => (
<div
className={getArray[i].className}
key={i}
style={{ height: `${bar.value * 0.1}vh` }}
></div>
))}
</div>
</>
);
};
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
export default SortingVisualizer;
I don't know what the best solution is, but a solution would be to use useRef.
The problem is related to Why am I seeing stale props or state inside my function? : On each render you are creating new functions for bubbleSort and combSort. Those functions use the value of getDelay that existed at the moment those functions have been created. When one of the buttons is clicked the "version" of the function of the last render will be executed, so the value of getDelay that existed then and there will be used.
Now, changing the slider will cause a rerender, and thus new versions of bubbleSort and combSort are created ... but those are not the versions that are currently running!
useRef solves that problem because instead of directly referring to the delay, we are referring to an object whose current property stores the delay. The object doesn't change, but the current property does and every time it's accessed we get the current value. I highly encourage you to read the documentation.
After your state variables, add
const delayRef = useRef(getDelay);
delayRef.current = getDelay
The second line keeps the ref in sync with the state.
Everywhere else where you reference getDelay, except value of the slider itself, use delayRef.current instead. For example:
if (delayRef.current > 0) await timer(delayRef.current / 2);
Demo (couldn't get it to work on SO): https://jsfiddle.net/wuf496on/

Shuffle array automatically 5 times

I have written JavaScript function, to shuffle an array of divs onClick.
// Function to shuffle 3 divs
shuffle = () => {
const shuffled = this.state.divs.sort(() => Math.random() - .50);
this.setState([...shuffled]);
};
// Button as an FYI
<button onClick={this.shuffle} className="has-text-black">shuffle hats</button>
This works absolutely fine, and randomises every time I click the button.
However, I want the divs to sort/shuffle 5 times automatically, onClick.
(IE = I don't want to have to click 5 times, to shuffle 5 times).
What's the best approach to do this?
(I've searched but haven't found anything to repeat shuffling on elements).
I thought about using async await/settimeout, to repeat this.state.divs.sort(() => Math.random() - .50) 5 times?
UPDATE:
To add context, here is a codesandbox...
https://codesandbox.io/s/distracted-shape-ifsiv
When I click the shuffle button, you can see the hats only swap positions once. Not 5 times.
Here is a posible way to do it, with javascript.
hats object get shuffled 5 times, with 200ms second transition.
Of course it's very simple, the object is meant to be extended!
let hats = [
{content: `<h6>1🎩</h6>`},
{content: `<h3>2🎩</h3>`},
{content: `<h1>3🎩</h1>`},
{content: `<h2>4🎩</h2>`},
{content: `<h4>5🎩</h4>`}
];
let timer;
function loop5(){
let i = 0
clearInterval(timer)
timer = setInterval(function(){
shuffle()
if (i >= 5){
clearInterval(timer)
}
i++
}, 200)
}
function shuffle(){
hats.sort(() => Math.random() - 0.5)
out.innerHTML = hats.map(e => e.content).join("")
}
div {display: flex; font-size: xx-large }
<button onclick="loop5()">shuffle hats</button>
<div id="out"></div>
I don't see why shuffling 5 times is better or any different than shuffling 5 times. Anyway you can do it in a naive way like this:
// Function to shuffle 3 divs
shuffle = () => {
const shuffled = this.state.divs.sort(() => Math.random() - .50);
this.setState([...shuffled]);
};
shuffleTimesFive = () => {
for(var i = 0; i < 5; i++)
this.shuffle();
}
// Button as an FYI
<button onClick={this.shuffleTimesFive} className="has-text-black">shuffle hats</button>
Or maybe a smarter way is to have a shuffleNTimes function that takes a parameter, like so:
// Function to shuffle 3 divs
shuffle = () => {
const shuffled = this.state.divs.sort(() => Math.random() - .50);
this.setState([...shuffled]);
};
shuffleNTimes = (n) => {
for(var i = 0; i < n; i++)
this.shuffle();
}
// Button as an FYI
<button onClick={this.shuffleNTimes.bind(this, 5)} className="has-text-black">shuffle hats</button>
I think shuffling one time and five times has the same effect, and Fisher-Yates is more efficient but keeping your way:
shuffle = () => {
let shuffled = [];
for(let i=0; i<5; i++)
shuffled = this.state.divs.sort(() => Math.random() - .50);
this.setState([...shuffled]);
};
If you decide to use "Fisher-Yates" algorithm, you can implement it like:
const shuffle = () => {
let array = this.state.divs;
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
this.setState([...array]);
}
As I understood from your comment, you want to do animation, in this case you don't need a loop rather you can use setInterval() and reset it ones executed five times. I have written a demo on both shuffling ways, as you can see the method that uses sort() sometimes returns the same result while the "Fisher–Yates" always reshuffled.
<button onclick="shuffle()">Click To Shuffle</button>
<div id="1">div1</div>
<div id="2">div2</div>
<div id="3">div3</div>
<div id="4">div4</div>
<script>
//This one uses Fisher–Yates shuffle algorithm:
const divs = [...document.querySelectorAll('div')];
const shuffle = () => {
let count = 0;
const intervalId = setInterval( function() {
for (let i = divs.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[divs[i], divs[j]] = [divs[j], divs[i]];
}
divs.forEach( div => document.body.appendChild(div) );
count++;
if(count === 5)
clearInterval(intervalId);
} ,1000)
}
</script>
<button onclick="shuffle()">Click To Shuffle</button>
<div id="1">div1</div>
<div id="2">div2</div>
<div id="3">div3</div>
<div id="4">div4</div>
<script>
const divs = [...document.querySelectorAll('div')];
shuffle = () => {
let shuffled = [];
let count = 0;
const intervalId = setInterval( function() {
shuffled = divs.sort(() => Math.random() - .50);
shuffled.forEach( div => document.body.appendChild(div) );
count++;
if(count === 5)
clearInterval(intervalId);
}, 1000 )
};
</script>
For your case, it would be like:
let divs = this.state.divs;
const shuffle = () => {
let count = 0;
const intervalId = setInterval( function() {
for (let i = divs.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[divs[i], divs[j]] = [divs[j], divs[i]];
}
this.setState([...divs]);
count++;
if(count === 5)
clearInterval(intervalId);
} ,1000)
}

Categories