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;
})
Related
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.
I add event handlers to all my inputs in a sudoku game using addEventListener.
Every time that I write a number inside an input handler is running 3 times, why?
I want it to run only once for each element.
How can I do that?
// passing the board game
function addListener(game) {
counter = 0;
for (let i = 0; i < 81; i++) {
//add evenListener to all the input filds
document.querySelectorAll('input')[i].addEventListener('input', function() {
//save the innerHtml of the value that we write
let inputInnerHTML = Number(this.value);
//save the id of the input that we write in
let index = this.id;
checkIfCorrect(inputInnerHTML, index, game)
})
};
}
function checkIfCorrect(inputInnerHTML, index, game) {
for (let i = 0; i < game.length; i++) {
//if the original number is the same, the number will be black
if (inputInnerHTML === 0) {
// if the input is erased remove the classes
document.getElementById(index).classList.remove('wrong');
document.getElementById(index).classList.remove('correct');
}
if (game[i] === inputInnerHTML) {
document.getElementById(index).classList.add('correct');
break;
} else if (game[i] !== inputInnerHTML && inputInnerHTML !== 0) {
//if the number is not the same, the number will be red
//and if we did not erase the number add red and count
document.getElementById(index).classList.add('wrong');
counter++
if (counter === 1) {
document.getElementById('mistakes').innerHTML = 'Mistakes: 1/3';
}
if (counter === 2) {
document.getElementById('mistakes').innerHTML = 'Mistakes: 2/3';
}
if (counter === 3) {
//go to you lose
document.getElementById('mistakes').innerHTML = 'Mistakes: 3/3';
youLose();
}
}
}
}
You are calling addEventListener every time you select a difficultyButton. This means that each time you click a button, you'll be adding an eventListener.
Fix this by calling removeEventListener, before calling addEventListener.
I think the problem is the checkIfCorrect function, after it is detected correct or not it doesn't stop and keep searching creating confusion.
By adding the break to the else if we block the search if the error has already been detected and notified.
function checkIfCorrect(inputInnerHTML, index, game) {
for (let i = 0; i < game.length; i++) {
//if the original number is the same, the number will be black
if (inputInnerHTML === 0) {
// if the input is erased remove the classes
document.getElementById(index).classList.remove('wrong');
document.getElementById(index).classList.remove('correct');
}
if (game[i] === inputInnerHTML) {
document.getElementById(index).classList.add('correct');
break;
} else if (game[i] !== inputInnerHTML && inputInnerHTML !== 0) {
//if the number is not the same, the number will be red
//and if we did not erase the number add red and count
document.getElementById(index).classList.add('wrong');
counter++
if (counter === 1) {
document.getElementById('mistakes').innerHTML = 'Mistakes: 1/3';
}
if (counter === 2) {
document.getElementById('mistakes').innerHTML = 'Mistakes: 2/3';
}
if (counter === 3) {
//go to you lose
document.getElementById('mistakes').innerHTML = 'Mistakes: 3/3';
youLose();
}
break; // FIX
}
}
}
I'm fairly new to javascript and looking at checking some fields with dynamic ID's at the end of the ID to see if they've either had values entered in all of them or none of them at all. The user shouldn't be allowed to only enter values in some of them and leave others blank.
I've wrote the below, which works, but I feel there must be a better way of doing this?:
var x = document.querySelectorAll('[id^="entryField"]');
for (var i = 0; i < x.length; ++i) {
if (x[i].value == "") {
for (var i = 0; i < x.length; ++i) {
if (x[i].value != "") {
alert("Please enter a value");
}
}
}
}
One loop should work with a counter for empty (or filled) fields. If the counter is not zero and does not have the length of the object, then some fields have a value.
var x = document.querySelectorAll('[id^="entryField"]'),
empty = 0;
for (var i = 0; i < x.length; ++i) {
if (x[i].value == "") {
++empty;
}
}
if (empty !== 0 && empty !== x.length) {
alert("Please enter a value");
}
This will be a simpler version:
var x = document.querySelectorAll('[id^="entryField"]');
const inputs = Array.from(x);
const allInput = inputs.every(input => {
return (input.value != "");
});
const allEmpty = inputs.every(input => {
return (input.value == "");
});
if (allInput || allEmpty) {
alert('xxxxxx');
}
ES5 implementation:
var x = document.querySelectorAll('[id^="entryField"]');
var inputs = Array.from(x);
var allInput = inputs.every(function(input) {
return (input.value != "");
});
var allEmpty = inputs.every(function(input) {
return (input.value == "");
});
if (allInput || allEmpty) {
alert('xxxxxx');
}
EDIT: Support allInput or allEmpty. Overlooked at the beginning.
You can check with two every calls, below will be true if all elements are filled or none of the elements are filled - every other case will be false:
var x = document.querySelectorAll('[id^="entryField"]');
var allowed = function allOrNone(elements) {
return Array.prototype.every.call(x, function(v) {
return v.value && v.value != "";
}) || Array.prototype.every.call(x, function(v) {
return !v.value || v.value == "";
});
}
console.log(allowed(x));
<input id="entryFieldFoo">
<input id="entryFieldBar">
I want to enter a "/" when user enters MM(2 digit) so it will be like MM/YYYY.
I have done similar for credit card number input which insert a space after 4 digit on keypress.
let ccNumber = e.target.value.split(" ").join("");
if (ccNumber.length > 0) {
ccNumber = ccNumber.match(new RegExp('.{1,4}', 'g')).join(" ");
}
e.target.value = ccNumber;
Fiddle
This works with
Regular keyboard input
Copy/Cut/Paste
Selected text
Adding the /
Because you're programmatically adding the / character, you have to update the cursor position whenever that affects the new input value. This can be more than one character if the user is pasting something. Most of the code complexity revolves around this issue.
There are a lot of comments in the code explaining the various situations that come up because of the /.
Full Code
var date = document.getElementById('date');
date.addEventListener('keypress', updateInput);
date.addEventListener('change', updateInput);
date.addEventListener('paste', updateInput);
date.addEventListener('keydown', removeText);
date.addEventListener('cut', removeText);
function updateInput(event) {
event.preventDefault();
var string = getString(event);
var selectionStart = this.selectionStart;
var selectionEnd = this.selectionEnd;
var selectionLength = selectionEnd - selectionStart;
var sanitizedString = string.replace(/[^0-9]+/g, '');
// Do nothing if nothing is added after sanitization
if (sanitizedString.length === 0) {
return;
}
// Only paste numbers that will fit
var valLength = date.value.replace(/[^0-9]+/g, '').length;
var availableSpace = 6 - valLength + selectionLength;
// If `/` is selected it should not count as available space
if (selectionStart <= 2 && selectionEnd >= 3) {
availableSpace -= 1;
}
// Remove numbers that don't fit
if (sanitizedString.length > availableSpace) {
sanitizedString = sanitizedString.substring(0, availableSpace);
}
var newCursorPosition = selectionEnd + sanitizedString.length - selectionLength;
// Add one to cursor position if a `/` gets inserted
if (selectionStart <= 2 && newCursorPosition >= 2) {
newCursorPosition += 1;
}
// Previous input value before current cursor position
var valueStart = date.value.substring(0, this.selectionStart);
// Previous input value after current cursor position
var valueEnd = date.value.substring(this.selectionEnd, date.value.length);
var proposedValue = valueStart + sanitizedString + valueEnd;
// Remove anything that's not a number
var sanitized = proposedValue.replace(/[^0-9]+/g, '');
format(sanitized);
this.setSelectionRange(newCursorPosition, newCursorPosition);
}
function removeText(event) {
if (event.key === 'Backspace' || event.type === 'cut') {
event.preventDefault();
var selectionStart = this.selectionStart;
var selectionEnd = this.selectionEnd;
var selectionLength = selectionEnd - selectionStart;
// If pressing backspace with no selected text
if (selectionLength === 0 && event.type !== 'cut') {
selectionStart -= 1;
// Remove number from before `/` if attempting to delete `/`
if (selectionStart === 2) {
selectionStart -= 1;
}
}
var valueStart = date.value.substring(0, selectionStart);
var valueEnd = date.value.substring(selectionEnd, date.value.length);
// Account for added `/`
if (selectionStart === 2) {
selectionStart += 1;
}
var proposedValue = valueStart + valueEnd;
var sanitized = proposedValue.replace(/[^0-9]+/g, '');
format(sanitized);
this.setSelectionRange(selectionStart, selectionStart);
}
}
function getString(event) {
if (event.type === 'paste') {
var clipboardData = event.clipboardData || window.clipboardData;
return clipboardData.getData('Text');
} else {
return String.fromCharCode(event.which);
}
}
function format(sanitized) {
var newValue;
var month = sanitized.substring(0, 2);
if (sanitized.length < 2) {
newValue = month;
} else {
var year = sanitized.substring(2, 6);
newValue = month + '/' + year;
}
date.value = newValue;
}
<input id="date" type="text" maxlength="7">
Try:
var date = document.getElementById('date');
date.addEventListener('keypress', function (event) {
var char = String.fromCharCode(event.which),
offset = date.selectionStart;
console.log(offset)
if (/\d/.test(char) && offset < 7) {
if (offset === 2) {
offset += 1;
}
date.value = date.value.substr(0, offset) + char + date.value.substr(offset + 1);
date.selectionStart = date.selectionEnd = offset + 1;
}
if (!event.keyCode) {
event.preventDefault();
}
});
<input id="date" type="text" value="mm/yyyy" maxlength="6" size="6">
function keypress(elem) { // get Input
if (typeof elem == 'string') {
if (document.getElementById(elem)) elem = document.getElementById(elem);
if (typeof elem == 'string') elem = document.getElementsByName(elem).item(0);
}
const el = elem; //handle error if not found input
el.maxLength = 19;
el.addEventListener('keypress', function (e) {
const t = e.keyCode || e.which
if (t == 8 || (t > 47 && t < 58)) { // limit numeric characters and backspace
if (t != 8) {
if (el.value.length == 2) el.value += '/';
if (el.value.length == 5) el.value += '/';
if (el.value.length == 10) el.value += ' ';
if (el.value.length == 13) el.value += ':';
if (el.value.length == 16) el.value += ':';
}
} else {
e.preventDefault();
}
});}
I have a form that I'm using to calculate some numbers, and the final 3 input fields on the form are disabled because they show the results of the calculator.
I'm using the following javascript/jquery to add commas to the user editable fields which works great but I can't seem to find a way to add commas to the "results" fields:
$('input.seperator').change(function(event){
// skip for arrow keys
if(event.which >= 37 && event.which <= 40){
event.preventDefault();
}
var $this = $(this);
var num = $this.val().replace(/,/gi, "").split("").reverse().join("");
var num2 = RemoveRougeChar(num.replace(/(.{3})/g,"$1,").split("").reverse().join(""));
// the following line has been simplified. Revision history contains original.
$this.val(num2);
});
function RemoveRougeChar(convertString){
if(convertString.substring(0,1) == ","){
return convertString.substring(1, convertString.length)
}
return convertString;
}
This is what I'm using the populate the fields, basically the fields show the results in dollars, so I'm trying to add a comma every 3 numbers:
$('#incorrect-payment').val(fieldK);
$('#correcting-payment').val(fieldL);
$('#total-cost').val(fieldM);
I think you'd want to use a function like this:
function FormatCurrency(amount, showDecimals) {
if (showDecimals == null)
showDecimals = true;
var i = parseFloat(amount);
if (isNaN(i)) { i = 0.00; }
var minus = false;
if (i < 0) { minus = true; }
i = Math.abs(i);
i = parseInt((i + .005) * 100);
i = i / 100;
s = new String(i);
if (showDecimals) {
if (s.indexOf('.') < 0) { s += '.00'; }
if (s.indexOf('.') == (s.length - 2)) { s += '0'; }
}
//s = minus + s;
s = '$' + FormatCommas(s, showDecimals);
if (minus)
s = "(" + s + ")";
return s;
}
function FormatCommas(amount, showDecimals) {
if (showDecimals == null)
showDecimals = true;
var delimiter = ","; // replace comma if desired
var a = amount.split('.', 2)
var d = a[1];
var i = parseInt(a[0]);
if (isNaN(i)) { return ''; }
var minus = '';
if (i < 0) { minus = '-'; }
i = Math.abs(i);
var n = new String(i);
var a = [];
while (n.length > 3) {
var nn = n.substr(n.length - 3);
a.unshift(nn);
n = n.substr(0, n.length - 3);
}
if (n.length > 0) { a.unshift(n); }
n = a.join(delimiter);
if (!showDecimals) {
amount = n;
}
else {
if (d.length < 1) { amount = n; }
else { amount = n + '.' + d; }
}
amount = minus + amount;
return amount;
}
May be you might want to trigger change event manually through javascript for your three read-only input fields. Using jquery trigger . I am not sure but it seems like a bad idea to have a read-only input field if no user can change these values. Usually having read-only input fields is good if a user with some security can edit those and some cannot.