useState in while cause infinite loop - javascript

I trying to make Blackjack game in React. A bot has got 2 cards at start. If user stands, and bots card value is less than 17, it should draw addictional card, but then program cause infinite loop. This is my code:
const [playerCards, setPlayerCards] = useState<Card[]>([]);
const shuffledDeck = shuffleDeck(deck);
const [dealerCards, setDealerCards] = useState<Card[]>([]);
const shuffleDeck = (deck: Card[]): Card[] => {
for (let i = deck.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[deck[i], deck[j]] = [deck[j], deck[i]];
}
return deck;
};
const handleStand = () => {
if (gameState === 'playing') {
console.log(shuffledDeck[playerCards.length + dealerCards.length]);
while(getHandValue(dealerCards) < 17) {
setDealerCards([
...dealerCards,
shuffledDeck[playerCards.length + dealerCards.length],
]);
}
}
let dealerHandValue = getHandValue(dealerCards);
const playerHandValue = getHandValue(playerCards);
if (dealerHandValue > 21 || dealerHandValue < playerHandValue) {
setGameState('won');
setPlayerBalance(playerBalance + currentBet);
} else if (dealerHandValue > playerHandValue) {
setGameState('lost');
setPlayerBalance(playerBalance - currentBet);
} else {
setGameState('tied');
}
};
const getHandValue = (cards: Card[]): number => {
let value = 0;
let numAces = 0;
for (const card of cards) {
if (card.rank === Rank.Ace) {
numAces++;
} else if (card.rank === 'J' || card.rank === 'K' || card.rank === 'Q') {
value += 10;
} else {
value += Number(card.rank);
}
}
while (numAces > 0) {
if (value + 11 > 21) {
value += 1;
} else {
value += 11;
}
numAces--;
}
return value;
};
The goal is to make bot draw a cards until value of his cards will be at least 17.

As discussed in the comments, using setDealerCards inside the while loop can be problematic. Setting the state causes a re-render to the component and may trigger the function again depending on it's use.
Another problem may be that since React schedules it's updates you may not get the most updated state every time you set the state again in the while loop.
So this might help
const handleStand = () => {
let newDealerCards = [...dealerCards];
if (gameState === 'playing') {
while(getHandValue(newDealerCards) < 17) {
newDealerCards =
[...newDealerCards, shuffledDeck[playerCards.length + newDealerCards.length]];
}
setDealerCards(newDealerCards);
}
let dealerHandValue = getHandValue(newDealerCards);
const playerHandValue = getHandValue(playerCards);
if (dealerHandValue > 21 || dealerHandValue < playerHandValue) {
setGameState('won');
setPlayerBalance(playerBalance + currentBet);
} else if (dealerHandValue > playerHandValue) {
setGameState('lost');
setPlayerBalance(playerBalance - currentBet);
} else {
setGameState('tied');
}
};
We make the new newDealerCards variable to spread dealerCards state, we then do the while loop with your condition and update newDealerCards accordingly. When we're done, only then we set the state again with the updated variable we made and thus we avoid calling setDealerCards multiple times inside the while loop.

Related

javascript I have a sequence that runs continuously, but need it to stop when an image is clicked

I have very little to no knowledge when it comes to using JavaScript. I have 24 of the same image given an id from q1 - q24. my code allows for the 24 images to be changed to image2 one at a time, but I need for it to stop and display a text/alert when image2 is clicked.
<script>
{
let num = 1;
function sequence()
{
let back = 1;
while (back < 25)
{
if(back == 1)
{
document.getElementById("q24").src = "question.jpg";
}
else
{
document.getElementById("q" + (back-1)).src = "question.jpg";
}
back++
}
document.getElementById("q" + num).src = "question2.png";
num = num + 1;
if(num > 24){num = 1;}
}
setInterval(sequence, 500);
}
</script>
Save the interval timer to a variable. Then add a click listener to all the images that stops the timer if the current image is the one showing question2.jpg.
{
let num = 1;
for (let i = 1; i <= 24; i++) {
document.getElementById(`q${i}`).addEventListener("click", function() {
if (i == num) {
clearInterval(interval);
}
});
}
let interval = setInterval(sequence, 500);
function sequence() {
for (let i = 1; i <= 24; i++) {
if (i == num) {
document.getElementById(`q${i}`).src = "question2.jpg";
} else {
document.getElementById(`q${i}`).src = "question.jpg";
}
num = num + 1;
if (num > 24) {
num = 1;
}
}
}
}
While I don't fully understand your use case, you could create a click event listener on the document and check the target's src in it.
document.addEventListener('click', function(e) {
if (e.target.src === 'question2.png') {
alert('Clicked question 2');
}
});

JavaScript - Nested FOR loop executing twice

I have a nested FOR loop that iterates over an object, and updating variables or attributes based on conditions. The last 'IF' on this loop formats a date and updates the field accordingly. For some reason the final line causes the loop to iterate twice, therefore updating the 'formattedDate' variable twice (in the object, there is only one single element with the 'childTag' = 'date'. Any help is appreciated!
for (let i = 0; i < submitFormData.tabs.length; i++) {
for (let j = 0; j < submitFormData.tabs[i].elements.length; j++) {
if (submitFormData.tabs[i].elements[j].childTag == "project_name") {
projectName = submitFormData.tabs[i].elements[j].value;
} else if (submitFormData.tabs[i].elements[j].childTag == "location_store") {
locationStore = ' #' + submitFormData.tabs[i].elements[j].value;
} else if (submitFormData.tabs[i].elements[j].childTag == "location_street") {
locationStreet = submitFormData.tabs[i].elements[j].value;
} else if (submitFormData.tabs[i].elements[j].childTag == "location_city") {
locationCity = submitFormData.tabs[i].elements[j].value;
} else if (submitFormData.tabs[i].elements[j].childTag == "location_state") {
locationState = submitFormData.tabs[i].elements[j].value;
} else if (submitFormData.tabs[i].elements[j].childTag == "location_zip") {
locationZip = submitFormData.tabs[i].elements[j].value;
} else if (submitFormData.tabs[i].elements[j].childTag == "date") {
if (submitFormData.tabs[i].elements[j].value != "") {
console.log(submitFormData)
date = (submitFormData.tabs[i].elements[j].value.substring(0, 10));
formattedDate = (date.substring(5, 11) + '-' + date.substring(0, 4));
console.log(formattedDate, 'formatted date')
submitFormData.tabs[i].elements[j].value = formattedDate;
}
}
}
}
Sample of Object
I see that in the loop you manipulate the list which you iterate over.
submitFormData.tabs[i].elements
It would be better to make a copy of it and iterate over it.
const newSubmitFormData = [...submitFormData.tabs[i].elements]
for (let j = 0; j < newSubmitFormData .length; j++) { /* your code */ }

Codecademy javascript Number guesser problem

So I am working on this project in codecademy that is a number guessing game. I am running into a bit of a kink. When the updateScore function fires, it says undefined. What it should do is update the humanScore variable or the computerScore variable based on the results of compareGuesses. When I put in the value of 'human' it says undefined when it should start adding up from zero. So the first time it is clicked it should be 1 and the second time should be 2 and so on. What am I forgetting or doing wrong?
let humanScore = 0;
let computerScore = 0;
let currentRoundNumber = 1;
// Write your code below:
function generateTarget() {
return Math.floor(Math.random() * 10);
};
function compareGuesses(humanGuess, computerGuess, generateTarget) {
let humanValue = Math.abs(humanGuess - generateTarget);
let computerValue = Math.abs (computerGuess - generateTarget);
if (humanValue < computerValue) {
return 'human';
} else if (humanValue === computerValue) {
return 'human';
} else {
return 'computer';
}
};
function updateScore(compareGuesses) {
if (compareGuesses === 'human') {
humanScore = humanScore + 1;
} else if (compareGuesses === 'computer') {
computerScore = computerScore + 1;
}
};
console.log(updateScore('human'));
You are passing functions as arguments to other functions and then treating them as a variable.
Here's an updated code:
let humanScore = 0;
let computerScore = 0;
let currentRoundNumber = 1;
// Write your code below:
function generateTarget() {
return Math.floor(Math.random() * 10);
};
function compareGuesses(humanGuess, computerGuess /*, generateTarget */) {
let generatedTarget = generateTarget(); // notice the "d"
let humanValue = Math.abs(humanGuess - generatedTarget);
let computerValue = Math.abs (computerGuess - generatedTarget);
if (humanValue <= computerValue) {
return 'human';
}
/* else if (humanValue === computerValue) {
return 'human';
} */
else {
return 'computer';
}
};
function updateScore(player) {
if (player === 'human') {
humanScore++; // = humanScore + 1;
}
else /* if (player === 'computer') */ {
computerScore++; // = computerScore + 1;
}
return "Round "+(currentRoundNumber++)+"\n"+
"Human: "+humanScore+"\n"+
"Computer: "+computerScore;
};
console.log(updateScore('human'));
You still need to fill-in the rest of the logic of the game.

How to allow only one decimal between operators (Calculator app)

I've been racking my brain trying to figure out how I can prevent more than one decimal for each number between arithmetic operators. Also, would it be more memory efficient to use event delegation when clicking a button instead of looping and adding event listeners? Any help would be greatly appreciated.
const display = document.querySelector(".result");
const number = document.getElementsByClassName("number-button");
const operator = document.getElementsByClassName("operator-button");
const equals = document.querySelector(".equals-button");
const clear = document.querySelector(".all-clear");
const backSpace = document.querySelector(".backspace");
let state = 0;
function reset() {
display.value = "";
}
// Iterates through number elements and sets event listeners
for (var i = 0; i < number.length; i++) {
number[i].addEventListener("click", function() {
if (state === 0) {
display.value += parseInt(this.value, 10);
} else if (state === 1) {
reset();
display.value += parseInt(this.value, 10);
state = 0;
}
})
}
// Iterates through operator elements and sets event listeners
for (var i = 0; i < operator.length; i++) {
operator[i].addEventListener("click", function() {
const prevIndex = display.value.substring(display.value.length - 1);
// if previous value is an operator, a new clicked operator will replace it
if (prevIndex === "*" || prevIndex === "+" || prevIndex === "-" || prevIndex === "/" || prevIndex === ".") {
display.value = display.value.slice(0, -1);
display.value += this.value;
} else if (display.value.length > 0) {
display.value += this.value;
state = 0;
}
})
}
// clears display value
clear.addEventListener("click", function() {
reset();
})
// deletes previous value on display
backSpace.addEventListener("click", function() {
display.value = display.value.slice(0, -1);
})
// Evaluates expression on display
equals.addEventListener("click", function() {
display.value = eval(display.value);
state = 1;
})

Unexpectedly passing integer by reference?

I'm having some issues with this. For some reason, updateCurrentItem is always called with the same parameter regardless.
function updatePromoList(query) {
query = query.toUpperCase();
clearCurrentList();
var numItems = 0;
currentItem = 0;
result_set = cached[query.charAt(0)][query.charAt(1)][query.charAt(2)];
for(i in result_set){
if(numItems >= NUMBER_MATCHES){
$("<li/>").html("<span style='color: #aaa'>Please try to limit your search results</span>").appendTo("#possibilities").mouseover(function(event){ updateCurrentItem(numItems+1); });
break;
}
promo = result_set[i];
found_number = false;
if (!promo.client)
found_number = (promo.prj_number.toString().substr(0,query.length) == query) ? true : false;
if (query.length >= MATCH_NAME) {
if(promo.prj_name && typeof promo.prj_name == 'string'){
found_name = promo.prj_name.toUpperCase().indexOf(query);
} else {
found_name = -1;
}
if (promo.client)
found_client = promo.client_name.toString().indexOf(query);
else
found_client = -1;
} else {
found_name = -1;
found_client = -1;
}
if(found_client >= 0) {
var thisIndex = numItems+1;
console.log("setting", thisIndex);
$("<li/>").text(promo.client_name).bind('click',function(e){ updatePromoChoice(e,promo); }).appendTo("#possibilities").mouseover(function(event){ updateCurrentItem(thisIndex); }); } else if(found_name >= 0 || found_number) { var thisIndex = numItems+1;
console.log("setting", thisIndex);
$("<li/>").text(promo.prj_number+": "+promo.prj_name).bind('click',function(e){ updatePromoChoice(e,promo); }).appendTo("#possibilities").mouseover(function(event){ updateCurrentItem(thisIndex); });
}
if(found_number || found_client >= 0 || found_name >= 0){
numItems++;
}
}
}
function updateCurrentItem(i){
currentItem = i;
console.log("updating to", i);
}
The results of running this are:
setting 0
setting 1
setting 2
setting 3
setting 4
setting 5
setting 6
setting 7
setting 8
setting 9
setting 10
setting 11
setting 12
setting 13
then when I move my mouse over the content area containing these <li>s with the mouseOver events, all I ever see is:
updating to 4
Always 4. Any ideas?
You're creating a closure but it's still bound to the numItems variable:
function(event){ updateCurrentItem(numItems+1); }
What you should do is something like this:
(function(numItems){return function(event){ updateCurrentItem(numItems+1); }})(numItems)
Edit: I think I might have the wrong function but the same principle applies:
function(event){ updateCurrentItem(thisIndex); }
should be
(function(thisIndex)
{
return function(event){ updateCurrentItem(thisIndex); }
})(thisIndex)

Categories