I'm trying to make a card matching game in JavaScript where every time the user starts the game, the cards shuffle. If they flip two of the same cards the get a point added to their score. I have the card matching aspect working using a data framework on each card div, but this immediately invoked shuffle card function I've added isn't working. Thank you in advance!
JavaScript of Shuffling the Cards:
const cards = document.querySelectorAll(".the-card");
(function shuffle() {
cards.forEach(card => {
let randomPos = Math.floor(Math.random() * 12);
card.style.order = randomPos;
});
})();
HTML of Each Card(16 in total):
<div class="main-card-container">
<div class="the-card" data-framework="twoofhearts">
<div class="the-back card-face">
<img src="./images/cardback.png" class="flipped-card"
</div>
<div class="the-front card-face">
<img src="./images/img-1.png" class="unflipped-card"
</div>
</div>
</div>
You can use the Fisher-Yates shuffle algorithm which runs in O(n), so there's no way to do it faster.
It's quite simple really:
var inOrder = [1, 2, 3, 4, 5, 6];
function fisherYatesShuffle(array){
for(let i = array.length -1; i >= 0; i--){
const newPosition = Math.floor((i + 1) * Math.random());
const temp = array[newPosition];
array[newPosition] = array[i];
array[i] = temp;
}
return array;
}
console.log(fisherYatesShuffle(inOrder));
On a side note: how random it really is does depend on how good your random numbers are. Math.random() is not cryptographically secure i.e. not all numbers are equally likely to be generated. If you want that you need to use Crypto.getRandomValues(). Math.random() is however much faster and should do for the randomness required in a game.
For more information on randomness see this blog post Baeldung CS - Understanding Randomness.
Related
I have two values on my html page, that I got from a form.
The person completes the amount of money they have on one input, and on the other input, the number of people.
So, my question is:
How do i divide the amount.value by the people.value, and distribute it, in a way it appears as shown in the example below?
Amount: 150 / Number of People: 3
-Person 1 - 50
-Person 2 - 50
-Person 3 - 50
What i'm actually struggling with, is to create a function that will add to the HMTL another person + the result, depending on the number of people added previously.
The code down below just finds the total share of all people and distributes the amount among them dependin on their shares.
/*----- Define your variables in here ------*/
var amount = 150;
var people = {
1: 0.75,
2: 0.15,
3: 0.10
}
var totalSharePerc = 0;
/*----- Loop thruogh people to calculate total sum ------*/
for (const [key, value] of Object.entries(people)) {
totalSharePerc = totalSharePerc + value;
}
/*----- Loop thruogh people and split the amount ------*/
for (const [key, value] of Object.entries(people)) {
/*------ Calculate share of person -----*/
var share = (amount*value/totalSharePerc);
/*----- Do whatever you want ------*/
console.log("Person"+key+" => "+share);
}
You can use Array#reduce() to calculate the total share for every person involved.
This assumes you have a mapping defined of which person has to cover which share (which you will need to have if you want to distribute in non-equal shares).
const amount = 150;
// define mapping of percentages to persons
const sharesPerPersonPct = {
1: 0.75,
2: 0.15,
3: 0.1,
};
// create a mapping of total values to persons
const sharesPerPersonTotal = Object.entries(sharesPerPersonPct).reduce(
(allShares, [personId, share]) => {
allShares[personId] = {
pct: share, // add percentage (optional)
total: amount * share // add total share
}
return allShares;
},
{}
);
console.log("Resulting JS object:")
console.log(sharesPerPersonTotal);
Object.entries(sharesPerPersonTotal).forEach(([personId, share]) => console.log(`Person ${personId} has to cover ${(share.pct * 100).toFixed(2)}% which amounts to ${share.total}$.`))
Updated answer to reflect your edit
The following is for an equal distribution of an amount to a number of people. The challenge is that e.g 10$ cannot be distributed 3.33$ for each of 3 persons as then penny would be missing. This is the sort of stuff you get when using floating point arithmetic. To prevent that use integer arithmetic instead. So multiply 10$ by 100 so you get 1000p and you can then assign each person their floored share (Math.floor(1000 / 3) = 333) use modulo to get the remainder (10 % 3 = 1) and distribute that remainder among the persons involved. The current implementation isn't quite fair either though because it always assigns that one penny more starting from the front, but you could use something like this to account for that.
The rest is just input validation using RegEx and displaying the results doing some DOM manipulation.
function handleUpdateDistribution() {
const amountMoney = document.getElementById("amount-money");
const noPersons = document.getElementById("no-persons");
if (!isMoneyValid(amountMoney.value)) {
console.log("Money value can only have two decimal places!");
return;
}
if (!isNoPersonValid(amountMoney.value)) {
console.log("Number of persons must be an integer greater than one!");
return;
}
const distribution = updateDistribution(
Number.parseInt(noPersons.value),
Number.parseFloat(amountMoney.value)
);
showDistribution(distribution);
}
function isMoneyValid(money) {
const matches = money.match(/^[0-9]+(\.[0-9]{1,2})?$/g);
if (matches === null) return null;
else return matches[0];
}
function isNoPersonValid(noPersons) {
const matches = noPersons.match(/[1-9]*/g);
if (matches === null) return null;
else return matches[0];
}
function showDistribution(distribution) {
const list = document.createElement("ul");
const listItems = Object.entries(distribution).map(([personId, share]) => {
const item = document.createElement("li");
item.textContent = `Person ${personId} has to cover ${share}$.`;
return item;
});
list.append(...listItems);
document.getElementById("result").replaceChildren(list);
}
/**
*
* #param {number} noPersons number of persons to split between
* #param {number} amountMoney amount of money to split
*/
function updateDistribution(noPersons, amountMoney) {
// use integer arithmetic as floating point arithmetic is not very suitable for task at hand
amountMoney *= 100;
const share = Math.floor(amountMoney / noPersons);
let remainder = amountMoney % noPersons;
const persons = {};
for (let i = 1; i <= noPersons; i++) {
const shareInInts = share + (remainder > 0 ? 1 : 0);
remainder--;
persons[i] = (shareInInts / 100).toFixed(2);
}
return persons;
}
window.addEventListener("DOMContentLoaded", (e) => {
const amountMoney = document.getElementById("amount-money");
const noPersons = document.getElementById("no-persons");
amountMoney.addEventListener("input", handleUpdateDistribution);
noPersons.addEventListener("input", handleUpdateDistribution);
});
input:invalid {
border: red solid 3px;
}
<form>
<input id="no-persons" placeholder="Number of persons" type="number" required />
<input id="amount-money" placeholder="Amount of money" type="text" pattern="^[0-9]+(\.[0-9]{1,2})?$" required />
</form>
<div id="result"></div>
Please note you can and should do more to give the user a nice user experience (Show popovers instead of console.log(), nicer styling etc). See MDN docs on form validation. You should also probably restrict the number of persons as rendering thousands of list items will seriously impact the performance.
The problem can be found here
I struggled with this for quite some time - and I've always neglected learning CS related material in Javascript or in general. But I will be interviewing at some different companies and some have notified that the tech interview will be data structure/algorithm related, so I'm currently going through some courses online and completing challenges on Hackerrank.
Anyway - I eventually caved after a few hours and looked up some solutions.
One being this:
let queue1 = [2, 1, 5, 3, 4];
let queue2 = [2, 5, 1, 3, 4];
function minimumBribes(q) {
let bribes = 0;
let currentIndex = q.length - 1;
let positionDiff;
while (currentIndex >= 0) {
const currentValue = q[currentIndex];
const valuePosition = currentIndex + 1;
positionDiff = currentValue - valuePosition;
if (positionDiff >= 3) {
return "Too chaotic";
}
const briberPosition = currentValue - 2;
for (let comparePosition = Math.max(0, briberPosition); comparePosition < currentIndex; comparePosition++) {
const compare = q[comparePosition];
if (compare > currentValue) {
bribes++;
}
}
currentIndex--;
}
return bribes;
}
minimumBribes(queue1)
I understand that we need to check if a number has moved forward more than 2 spaces, and checking its value vs position does this.
However, getting to
const briberPosition = currentValue - 2;
for (let comparePosition = Math.max(0, briberPosition); comparePosition < currentIndex; comparePosition++) { ... }
is leaving me confused. I've ran through this slowly in debugger with different values and I can't wrap my head around why we set the currentValue - 2 to the briberPosition, and then checking that vs the index.
Could someone share some light?
The final question of the challenge is "HOW MANY bribes are needed to get the queue into this state".
So he chose to go from the end on every number and first checks if its 3 or more places ahead of its original position- this part you already got.
Now, if its not 3 or more places away- you want to know HOW many places away, this can be done simply by going 2 places back, and check if the numbers 2 places away and 1 place away are greater than the value in our current position. If they are- it means they bribed him to get there so we increment the "bribes" variable and go on to the next integer and repeat the process. Since he goes at the array from the end, he decrements the position to get to the farthest place a "briber" of our current value can be.
If it helps, this whole for loop can be completely skipped when positionDiff is 0, if he checked that after the too chaotic check, it could be a little more efficient.
Notice that in the "for" loop he trims it to be the Math.max(0, briberPosition) to avoid going beyond array limit.
I'm trying to have the numbers 1-6 quickly flash on the screen when a button is clicked, then stop and display the random generated number. If I put clearInterval into the function it just displays the random Number and doesn't display the flashes up numbers before hand.
HTML
<div id='dice'>
<div id='number'>0</div>
</div>
<div id='button'>
<button onclick='roll()'>Roll Dice</button>
</div>
JAVASCRIPT
let rollButton = document.querySelector('button');
let diceNumber = document.getElementById ('number');
function roll(){
diceSides = [1,2,3,4,5,6];
var i = 0;
let shuffleDice = setTimeout(function(){
diceNumber.innerHTML = diceSides[i++];
if(diceNumber == diceSides.length){
i = 0;
}
}, 500);
let random = Math.floor(Math.random() * 6);
diceNumber.innerHTML = random;
}
Maybe this works for you
hint: you can use i as a counter variable OR use your approach (define an array with numbers and use index to find them and put them in number tag)
HTML:
<div id='dice'>
<div id='number'>0</div>
</div>
<div id='button'>
<button onclick='roll()'>Roll Dice</button>
</div>
JS:
let rollButton = document.querySelector('button');
let diceNumber = document.getElementById ('number');
function roll(){
diceSides = [1,2,3,4,5,6];
var i = 6;
let shuffleDice = setInterval(function(){
diceNumber.innerHTML = i;
if(i == 0){
clearInterval(shuffleDice);
let random = Math.floor(Math.random() * 6);
diceNumber.innerHTML = random;
}
i--;
}, 1000);
}
CodePen
clearInterval
setInterval
setTimeout
SetTimeout only executes once. Also your variable is changed by the interval!
After its over, you have to clear the interval with clearInterval.
let rollButton = document.querySelector('button');
let diceNumber = document.getElementById ('number');
function roll(){
diceSides = [1,2,3,4,5,6];
var i = 0;
var shuffleDice = setInterval(function(){
diceNumber.innerHTML = diceSides[i++];
//use i
if(i == diceSides.length){
i = 0;
//clear
clearInterval(shuffleDice);
// moved because the interval will change it
let random = Math.floor(Math.random() * 6);
diceNumber.innerHTML = String(random);
}
}, 500);
}
<div id = 'dice'>
<div id = 'number'>0</div>
</div>
<div id = 'button'>
<button onclick = 'roll()'>Roll Dice</button>
</div>
I suggest something like this:
var diceNumber = document.getElementById ('number');
const diceSides = [1,2,3,4,5,6];
function roll(){
let arr = [...diceSides,diceSides[Math.floor(Math.random() * diceSides.length)]];
cycle(diceNumber,arr,200);
}
function cycle(element,arr,delay) {
element.innerText=arr.shift();
if (arr.length > 0)
setTimeout(cycle,delay,element,arr,delay);
}
Given that you know precisely the list you want to iterate through, and thus the fixed number of iterations you want to execute, this seems cleaner and more concise than setInterval, which you would have to explicitly stop when you reach the end of the list. I like setInterval for things that will run an indeterminate period of time (for instance, until stopped by a user action).
What this says is: "Take this list of numbers (the last one being the random one). Show the first one and remove it from the list. Then wait a while and do it again, until you're out of numbers."
A couple other fine points here:
innerText rather than innerHTML, since you're just setting string content.
You want to use your random number as a key against your array of die faces, not directly - used directly, you get [0-5], not [1-6].
Use the length of your array, rather than hard-coding '6' - always avoid magic numbers when you can. By referring everything back to your constant array, changing the values on the faces of the die (or the number of them) becomes trivial; just change the array and everything else will still work.
Normally, there are stack-size concerns with recursive code. In this case, that wouldn't be a problem because of the small size of the array. But beyond that, the fact that the recursion is going through setTimeout means that each one is a seprate entry on the queue, and the prior isn't waiting for the next to complete before it can exit.
I'm trying to change the following (that currently returns a random number from an array), so that each random number is different from the last one chosen.
function randomize(arr) {
return arr[Math.floor(Math.random()*arr.length)];
}
oracleImg = [];
for (var i=1;i<=6;i++) {
oracleImg.push(i);
}
randOracleImg = randomize(oracleImg);
I tried the following, but it's not always giving me a number different from the last number.
function randomize(arr) {
var arr = Math.floor(Math.random()*arr.length);
if(arr == this.lastSelected) {
randomize();
}
else {
this.lastSelected = arr;
return arr;
}
}
How can I fix this?
Your existing function's recursive randomize() call doesn't make sense because you don't pass it the arr argument and you don't do anything with its return value. That line should be:
return randomize(arr);
...except that by the time it gets to that line you have reassigned arr so that it no longer refers to the original array. Using an additional variable as in the following version should work.
Note that I've also added a test to make sure that if the array has only one element we return that item immediately because in that case it's not possible to select a different item each time. (The function returns undefined if the array is empty.)
function randomize(arr) {
if (arr.length < 2) return arr[0];
var num = Math.floor(Math.random()*arr.length);
if(num == this.lastSelected) {
return randomize(arr);
} else {
this.lastSelected = num;
return arr[num];
}
}
document.querySelector("button").addEventListener("click", function() {
console.log(randomize(["a","b","c","d"]));
});
<button>Test</button>
Note that your original function seemed to be returning a random array index, but the code shown in my answer returns a random array element.
Note also that the way you are calling your function means that within the function this is window - not sure if that's what you intended; it works, but basically lastSelected is a global variable.
Given that I'm not keen on creating global variables needlessly, here's an alternative implementation with no global variables, and without recursion because in my opinion a simple while loop is a more semantic way to implement the concept of "keep trying until x happens":
var randomize = function () {
var lastSelected, num;
return function randomize(arr) {
if (arr.length < 2) return arr[0];
while (lastSelected === (num = Math.floor(Math.random()*arr.length)));
lastSelected = num;
return arr[num];
};
}();
document.querySelector("button").addEventListener("click", function() {
console.log(randomize(["a","b","c","d"]));
});
<button>Test</button>
Below code is just an example, it will generate 99 numbers and all will be unique and random (Range is 0-1000), logic is simple just add random number in a temporary array and compare new random if it is already generated or not.
var tempArray = [];
var i=0;
while (i != 99) {
var random = Math.floor((Math.random() * 999) + 0);
if (tempArray.indexOf(random)==-1) {
tempArray.push(random);
i++;
} else {
continue;
}
}
console.log(tempArray);
here is a version which will ensure a random number that is always different from the last one. additionally you can control the max and min value of the generated random value. defaults are max: 100 and min: 1
var randomize = (function () {
var last;
return function randomize(min, max) {
max = typeof max != 'number' ? 100 : max;
min = typeof min != 'number' ? 1 : min;
var random = Math.floor(Math.random() * (max - min)) + min;
if (random == last) {
return randomize(min, max);
}
last = random;
return random;
};
})();
If you want to ALWAYS return a different number from an array then don't randomize, shuffle instead!*
The simplest fair (truly random) shuffling algorithm is the Fisher-Yates algorithm. Don't make the same mistake Microsoft did and try to abuse .sort() to implement a shuffle. Just implement Fisher-Yates (otherwise known as the Knuth shuffle):
// Fisher-Yates shuffle:
// Note: This function shuffles in-place, if you don't
// want the original array to change then pass a copy
// using [].slice()
function shuffle (theArray) {
var tmp;
for (var i=0; i<theArray.length;i++) {
// Generate random index into the array:
var j = Math.floor(Math.random()*theArray.length);
// Swap current item with random item:
tmp = theArray[i];
theArray[j] = theArray[i];
theArray[i] = tmp;
}
return theArray;
}
So just do:
shuffledOracleImg = shuffle(oracleImg.slice());
var i=0;
randOracleImg = shuffledOracleImg[i++]; // just get the next image
// to get a random image
How you want to handle running out of images is up to you. Media players like iTunes or the music player on iPhones, iPads and iPods give users the option of stop playing or repeat from beginning. Some card game software will reshuffle and start again.
*note: One of my pet-peeves is music player software that randomize instead of shuffle. Randomize is exactly the wrong thing to do because 1. some implementations don't check if the next song is the same as the current song so you get a song played twice (what you seem to want to avoid) and 2. some songs end up NEVER getting played. Shuffling and playing the shuffled playlist from beginning to end avoids both problems. CD player manufacturers got it right. MP3 player developers tend to get it wrong.
I'm having some trouble getting an array to be filled properly. I'm attempting to get a deck of cards to be loaded into an array and then shuffled which does work fine initially, however, after I do a check to see if there are enough cards left, the array does not load properly and everything more or less breaks.
Here's the relevant code. Any help would be greatly appreciated. Thanks!
var deck = {
//base deck before shuffle
baseDeck: ['d02', 'd03', 'd04', 'd05', 'd06', 'd07', 'd08', 'd09', 'd10', 'd11', 'd12', 'd13', 'd14', 'h02', 'h03', 'h04', 'h05', 'h06', 'h07', 'h08', 'h09', 'h10', 'h11', 'h12', 'h13', 'h14', 'c02', 'c03', 'c04', 'c05', 'c06', 'c07', 'c08', 'c09', 'c10', 'c11', 'c12', 'c13', 'c14', 's02', 's03', 's04', 's05', 's06', 's07', 's08', 's09', 's10', 's11', 's12', 's13', 's14'],
//Deck Shoe
shoe: [],
//pull deck #, return to shoe
shuffleDeck: function () {
this.shoe.length = 0;
this.shoe = this.baseDeck;
for (i = 0; i < this.shoe.length; i++) {
var randomPlace = Math.floor(Math.random() * 50) + 1;
var currentPlace = this.shoe[i];
this.shoe[i] = this.shoe[randomPlace];
this.shoe[randomPlace] = currentPlace;
}
}
}
var cardRetrieval = {
//return card vals
getCard: function (curHand) {
var q = curHand.push(deck.shoe.shift());
this.checkValue(curHand);
showCards(curHand, q);
if (deck.shoe.length <= 40) {
deck.shuffleDeck();
}
}
Everything works fine until the if statement at the bottom that checks if there are 40+ cards in the shoe array. But when it attempts to shuffle the deck again, it breaks.
The trouble is with this:
this.shoe.length = 0;
this.shoe = this.baseDeck;
You're not making a copy of the baseDeck into the shoe. Instead you're overwriting the reference to the empty Array you created for shoe, and you're replacing it with a reference to the same Array that baseDeck references.
So it works the first time you shuffle, because this.shoe.length = 0 is not yet affecting the baseDeck. But when you shuffle the second time, you're destroying the baseDeck. (Basically, with that first shuffle, you were using the baseDeck instead of a copy of it.)
Change it to this:
this.shoe.length = 0;
this.shoe = this.baseDeck.slice(0);
This will make a clean copy of baseDeck that is referenced by shoe each time.
There are 2 problems with your random number, 1) it will never be 0 - the first card in the deck, and 2) it may exceed the array size.
Use this instead:
var randomPlace = Math.floor(Math.random() * this.shoe.length);