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

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

Related

innerHTML returns undefined - JavaScript

I'm creating a game on the web for my studies. I need to create a card game where each card has its own data (number and power name), data is taken from data.txt and put in an array that is then randomized. data.txt looks something like this:
0
Bored cat
-8
Biting
-10
Scratching
Play() function scrambles the deck when the PLAY button is pressed.
Once all cards are randomized I call a function addCardCat() which should create a card div with h1 and h3 tags. In h1 tag innerHTML should be a number, and h3 should be the power name, however, I always get returned undefined.
What could be the issue?
JS CODE
let counter = 0;
var deck = [];
// ----------READING----------//
let fs = require('fs');
let data = fs.readFileSync('data.txt', 'utf8').split('\r\n');
// ----------RANDOM NUMBER GENERATOR----------//
let numberArray = Array.from(Array(54).keys())
// ----------ARRAY SCRAMBLE---------//
function scrambleArray(array){
let currentIndex = array.length, randomIndex;
// While there remain elements to shuffle...
while (currentIndex != 0){
// Pick a remaining element...
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
// And swap it with the current element.
[array[currentIndex], array[randomIndex]] = [
array[randomIndex], array[currentIndex]];
}
return array;
}
// ----------PLAYING DECK----------//
function scrambleDeck(array){
for(var i = 0; i < 54; i++){
var j = numberArray[i];
array.push(data[j*2]);
array.push(data[j*2+1]);
}
return array;
}
// ----------ADD CARD----------//
function addCardCat(number, power) {
counter++;
var newDiv = document.createElement("div");
newDiv.className += " cat__card";
newDiv.id = 'card-' +counter;
// newDiv.onclick = removeCardCat();
// newDiv.onclick = function() { alert('blah'); };
document.getElementById('cat').appendChild(newDiv);
var newH1 = document.createElement("h1");
var newH3 = document.createElement("h3");
document.getElementById('card-' +counter).appendChild(newH1);
document.getElementById('card-' +counter).appendChild(newH3);
newH1.innerHTML = number;
newH3.innerHTML = power;
return number, power;
}
// ----------START GAME----------//
function play() {
document.getElementById("play").style.display = "none";
scrambleArray(numberArray);
scrambleDeck(deck);
}```
It's hard to tell with the subminimal code provided, but it is likely that this return number, power; is causing your issue.
The return statement ends function execution and specifies a value to be returned to the function caller.
MDN
The return statement will only return a single value. You cannot use a comma-separated list. See the first snippet below for a demonstration of what happens if you use a comma-separated list.
let x = function(number, power) {
//Do stuff
return number, power;
}
console.log(x(2, 5));
Notice that you only got back the last value in the comma-separated list.
You can, however, wrap the 2 values in an array or object, for example, and then return the array or object. You can then use the appropriate destructuring method to extract the 2 values from the array or object.
Like this:
let x = function(number, power) {
//Do stuff
return [number, power];
}
console.log(x(2, 5));
That being said, it does seem odd that you are returning the same values you passed to the function as arguments without modifying them in any way.
Depending on what you do with the returned values, which unfortunately you don't show in your question, you will need to do one of the following:
Use the method I described.
Return a valid single value (including true or false) as needed.
Eliminate the return statement.

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]

How to select a number of random files from a given path using Node.js?

I am trying to select a number of random files from a given directory. Below is my current implementation; however, there are too many files inside the folder iterating them all and then pick few random ones seems overkill.
Is there a better solution? Because what I am thinking is knowing all the files inside the folder is the precondition for random selection?
const dirs = fs.readdirSync(IMAGE_BANK_SRC)
.map(file => {
return path.join(IMAGE_BANK_SRC, file);
});
const srcs_dup = [];
dirs.forEach(path => {
fs.readdirSync(path).forEach(file => {
srcs_dup.push(file);
});
});
// Pick few random ones from `srcs_dup`
Requirements:
The selected random files should be unique
The code is still working if the folder contains less files than expected
As fast as possible
Well, readDir & readDirSync return an array. You could avoid mapping through the entire array of paths by using the length property. We can make a dynamic sample set using a percentage of the length, then store the samples in a new array.
const dirs = fs.readdirSync(IMAGE_BANK_SRC);
const length = dirs.length;
const sampleSet = 25/100 * length;
const getRandomIndex = length => Math.floor( Math.random() * length );
let samples = [];
let usedIndices = [];
let randomIndex = undefined;
for (let i = 0; i < sampleSet; i++){
do {
randomIndex = getRandomIndex( length );
}
while ( usedIndices.includes( randomIndex ) );
usedIndicies.push( randomIndex );
samples.push( dirs[randomIndex] );
}
Basically in the below code, I created randomIndex() which grabs a random file index. After you get the list of files. I do a while loop to grab a random file from the directory list and add it to the array.
//Grabs a random index between 0 and length
function randomIndex(length) {
return Math.floor(Math.random() * (length));
}
//Read the directory and get the files
const dirs = fs.readdirSync(IMAGE_BANK_SRC)
.map(file => {
return path.join(IMAGE_BANK_SRC, file);
});
const srcs_dup = [];
const hashCheck = {}; //used to check if the file was already added to srcs_dup
var numberOfFiles = dirs.length / 10; //OR whatever # you want
//While we haven't got the number of files we want. Loop.
while (srcs_dup.length < numberOfFiles) {
var fileIndex = randomIndex(dirs.length-1);
//Check if the file was already added to the array
if (hashCheck[fileIndex] == true) {
continue; //Already have that file. Skip it
}
//Add the file to the array and object
srcs_dup.push(dirs[fileIndex]);
hashCheck[fileIndex] = true;
}
console.log(srcs_dup); //The list of your files
If this doesn't work. Let me know.
Here's a simplistic implementation. You should also consider using the path.resolve() method.
const dirs = fs.readdirSync(IMAGE_BANK_SRC)
.map((e) => { return path.join(IMAGE_BANK_SRC, e); });
// New random list of dirs
const randomList = dirs.slice(0)
.map((e) => { return Math.random() < .5 ? e : null; })
.filter((e) => { return e != null; });
First, you no need to map to concat your directory path, this will loop through entire file 1 time.
Second, just loop number of files you need
let result = []
let requiredCount = 3;
let files = fs.readdirSync(IMAGE_BANK_SRC)
while(requiredCount-- && files.length) {
let length = files.length;
let selectedIndex = Math.floor(Math.random() * length)
let selected = files.splice(selectedIndex, 1);
result.push(path.join(IMAGE_BANK_SRC, selected))
}

avoid same value to appear again using math.random()

animations = ['fadeIn','fadeInDown','slideInUp','flipInY','bounceInLeft'];
Imagine I generate random effect whenever user click something, so to achieve best experience, I would want the user to have same effect. But with
animations[ Math.floor(Math.random() * animations.length) -1];
that would happens.
How to avoid same value to appear again?
Two ways that i can suggest.
First shuffle the array and go one by one from index 0 to 5 and then loop as much as you like.
Pick a random element and slice it out up until the array is empty and then refresh your array from a back up. (be careful not to back up with a reference or your backup array gets deleted along with the one gets spliced. so use .slice())
Array.prototype.shuffle = function(){
var a = this.slice(), // don't morph the original
i = a.length,
j;
while (i > 1) {
j = ~~(Math.random()*i--);
a[i] = [a[j],a[j]=a[i]][0];
}
return a;
};
var album = ["photo1","photo2","photo3","photo4","photo5"];
photos = album.shuffle();
photos.forEach(p => console.log(p));
console.log("another way") // the splice way
photos = album.slice();
while (photos.length) console.log(photos.splice(Math.floor(Math.random() * photos.length),1)[0]);
!photos.length && (photos = album.slice()); // restore photos album and continue
while (photos.length) console.log(photos.splice(Math.floor(Math.random() * photos.length),1)[0]);
!photos.length && (photos = album.slice()); // restore photos album and continue
Following #Redu and my comments, take it out after you use it, but work on a copy.
var animations = ['fadeIn', 'fadeInDown', 'slideInUp', 'flipInY', 'bounceInLeft'];
var j;
var tmp = animations.slice(); //copy
var removed = 0;
for (var i = 1; i < 20; i++) {
j = Math.floor(Math.random() * tmp.length);
console.log(tmp[j]);
tmp.splice(j, 1);
removed++;
if (animations.length == removed) {
tmp = animations.slice();
removed = 0
}
}
I suggest to use a different method, by storing the last two selected elements and choose a different from the last selected items.
That prevent slicing and manipulation of original array.
function Random(array) {
var last = [];
this.next = function () {
var r;
do {
r = Math.floor(Math.random() * array.length);
} while (~last.indexOf(r))
last.length === 2 && last.shift();
last.push(r);
return array[r];
}
}
var animations = ['fadeIn', 'fadeInDown', 'slideInUp', 'flipInY', 'bounceInLeft'],
random = new Random(animations),
i;
for (i = 0; i < 15; i++) {
console.log(random.next());
}
.as-console-wrapper { max-height: 100% !important; top: 0; }

How to pick a random property from an object without repeating after multiple calls?

I'm trying to pick a random film from an object containing film objects. I need to be able to call the function repeatedly getting distinct results until every film has been used.
I have this function, but it doesn't work because the outer function returns with nothing even if the inner function calls itself because the result is not unique.
var watchedFilms = [];
$scope.watchedFilms = watchedFilms;
var getRandomFilm = function(movies) {
var moviesLength = Object.keys(movies).length;
function doPick() {
var pick = pickRandomProperty(movies);
var distinct = true;
for (var i = 0;i < watchedFilms.length; i += 1) {
if (watchedFilms[i]===pick.title) {
distinct = false;
if (watchedFilms.length === moviesLength) {
watchedFilms = [];
}
}
}
if (distinct === true) {
watchedFilms.push(pick.title);
return pick;
}
if (distinct === false) {
console.log(pick.title+' has already been picked');
doPick();
}
};
return doPick();
}
T.J. Crowder already gave a great answer, however I wanted to show an alternative way of solving the problem using OO.
You could create an object that wraps over an array and makes sure that a random unused item is returned everytime. The version I created is cyclic, which means that it infinitely loops over the collection, but if you want to stop the cycle, you can just track how many movies were chosen and stop once you reached the total number of movies.
function CyclicRandomIterator(list) {
this.list = list;
this.usedIndexes = {};
this.displayedCount = 0;
}
CyclicRandomIterator.prototype.next = function () {
var len = this.list.length,
usedIndexes = this.usedIndexes,
lastBatchIndex = this.lastBatchIndex,
denyLastBatchIndex = this.displayedCount !== len - 1,
index;
if (this.displayedCount === len) {
lastBatchIndex = this.lastBatchIndex = this.lastIndex;
usedIndexes = this.usedIndexes = {};
this.displayedCount = 0;
}
do index = Math.floor(Math.random() * len);
while (usedIndexes[index] || (lastBatchIndex === index && denyLastBatchIndex));
this.displayedCount++;
usedIndexes[this.lastIndex = index] = true;
return this.list[index];
};
Then you can simply do something like:
var randomMovies = new CyclicRandomIterator(Object.keys(movies));
var randomMovie = movies[randomMovies.next()];
Note that the advantage of my implementation if you are cycling through items is that the same item will never be returned twice in a row, even at the beginning of a new cycle.
Update: You've said you can modify the film objects, so that simplifies things:
var getRandomFilm = function(movies) {
var keys = Object.keys(movies);
var keyCount = keys.length;
var candidate;
var counter = keyCount * 2;
// Try a random pick
while (--counter) {
candidate = movies[keys[Math.floor(Math.random() * keyCount)]];
if (!candidate.watched) {
candidate.watched = true;
return candidate;
}
}
// We've done two full count loops and not found one, find the
// *first* one we haven't watched, or of course return null if
// they've all been watched
for (counter = 0; counter < keyCount; ++counter) {
candidate = movies[keys[counter]];
if (!candidate.watched) {
candidate.watched = true;
return candidate;
}
}
return null;
}
This has the advantage that it doesn't matter if you call it with the same movies object or not.
Note the safety valve. Basically, as the number of watched films approaches the total number of films, our odds of picking a candidate at random get smaller. So if we've failed to do that after looping for twice as many iterations as there are films, we give up and just pick the first, if any.
Original (which doesn't modify film objects)
If you can't modify the film objects, you do still need the watchedFilms array, but it's fairly simple:
var watchedFilms = [];
$scope.watchedFilms = watchedFilms;
var getRandomFilm = function(movies) {
var keys = Object.keys(movies);
var keyCount = keys.length;
var candidate;
if (watchedFilms.length >= keyCount) {
return null;
}
while (true) {
candidate = movies[keys[Math.floor(Math.random() * keyCount)]];
if (watchedFilms.indexOf(candidate) === -1) {
watchedFilms.push(candidate);
return candidate;
}
}
}
Note that like your code, this assumes getRandomFilm is called with the same movies object each time.

Categories