I recently started to study javascript
I'm currently watching Javascript course in Udemy.
While code challenging, There's something I cant get it about parameter of 'switch'
let john = {
fullName: 'John Smith',
bills: [124, 48, 268, 180, 42],
calcTips: function() {
this.tips = [];
this.finalValues = [];
for (let i = 0; i < this.bills.length; i++) {
let percentage;
let bill = this.bills[i]
switch (bill) { // If I put parameter as 'bill' variation, The result is only defalut.
case bill < 50:
percentage = 0.2;
break;
case bill >= 50 && bill < 200:
percentage = 0.15;
break;
default:
percentage = 0.1;
}
this.tips[i] = bill * percentage;
this.finalValues[i] = bill + bill * percentage;
}
}
}
john.calcTips();
console.log(john);
However
let john = {
fullName: 'John Smith',
bills: [124, 48, 268, 180, 42],
calcTips: function() {
this.tips = [];
this.finalValues = [];
for (let i = 0; i < this.bills.length; i++) {
let percentage;
let bill = this.bills[i]
switch (true) { // If I put 'ture' as a parameter, It work's. Why?
case bill < 50:
percentage = 0.2;
break;
case bill >= 50 && bill < 200:
percentage = 0.15;
break;
default:
percentage = 0.1;
}
this.tips[i] = bill * percentage;
this.finalValues[i] = bill + bill * percentage;
}
}
}
john.calcTips();
console.log(john);
I've searched in google about this problem.
But I can't find specific way to solve this issue.
I'll appreciate your help.
Switch statements compare values strictly. Which means that you can compare for the exact value of the switch variable.
switch (x) {
case 1: console.log(1); break;
case 2: console.log(2); break;
}
You can do a trick however if you want to make the switch statement work on numerical ranges like this:
var x = this.dealer;
switch (true) {
case (x < 5):
alert("less than five");
break;
case (x < 9):
alert("between 5 and 8");
break;
case (x < 12):
alert("between 9 and 11");
break;
default:
alert("none");
break;
}
The implementation works on the strict comparison of booleans. The switch statement is for true and will match wherever the case is true.
Related question: Switch on ranges of integers in JavaScript
The switch statement tests the value of a variable and compares it with multiple cases. Once the case match is found, a block of statements associated with that particular case is executed. So in this case you switching on a constant value.
More detail :
javascript: using a condition in switch case
Related
I'm a beginner in javascript, so apologies in advanced if this is a dumb question. So, I've noticed that this question has been asked before, but that question was using IF ELSE statements. I'm trying to do it with SWITCH instead.
What i wanted to know is whether i need to also type in RETURN before count++ or count-- inside the SWITCH. I was using a website that provided this question, and my only error was that i used RETURN. The other post used IF ELSE IF and used RETURN.
Can anybody explain why i didn't need to use RETURN?
let count = 0;
function cc(card) {
switch (card){
case 2:
case 3:
case 4:
case 5:
case 6:
count++;
break;
case 7: case 8: case 9:
count;
break;
case 10: case "J": case "Q": case "K": case "A":
return count--;
break;
}
if (count>0){
return count+" Bet";
} else {
return count+" Hold";
}
}
Update
Do not add return to a switch()'s case block
Interrupting the flow gives you no advantage and more than likely error prone code that will not log any runtime errors.
In Example C are three functions:
Function
Description
Icon
A(card)
The original version of cc(card) without the added returns
👍
B(card)
The modified version of cc(card) with returns short circuiting each case
⛔
C(card)
The modified version of B(card) with prefixed operators that fix B(card)
⚠️
switch() is Fragile
A return before the break is a short circuit. Short circuit in programming context is inserting an early termination of a function/method when a condition is meet. I use this technique frequently but never in a switch(). switch() is a very readable yet bulky function if you deviate from how it's normally used it will usually cause a hiccup -- something inaccurate or incorrect and too slight to notice since it's not a runtime error.
Take for instance the line marked with a warning sign ⚠️ in Example A (also abstracted in Figure I)
Figure I
return count--;
break;
So any 10 or face card is short circuited so it never reaches break nor will it reach the last condition that determines whether to bet
or hold which is the whole point of this function. Now if we were to pass an A♤:
Figure II
cc('A');
// returns a 0 / expected -1
Why did it not decrement count? Because it never got the chance to be evaluated due to being short circuited. Originally, all cards went through the switch got it's value then when it reached the last condition, it would be evaluated and it's current values set to 1, 0, or -1. If you have your heart set on using returns before a break, change the counters from post to pre (see Figure III).
Figure III
return ++counter;
When the in/decrement is prefixed to the value, it is evaluated before it's stored so you'll get accurate results. Like I said, there's no point in using the switch() if return is used within it.
Example A is the OP (Original Post)
Example B is a refactor of OP
Example C is a side-by-side comparison between OP with short circuit 👎 and OP without short circuit 👍
Example A
Original Answer
let count = 0;
function cc(card) {
switch (card) {
case 2:
case 3:
case 4:
case 5:
case 6:
count++;
break;
case 7:
case 8:
case 9:
count;
break;
case 10:
case "J":
case "Q":
case "K":
case "A":
return count--; // ⚠️
break;
}
if (count > 0) {
return count + " Bet";
} else {
return count + " Hold";
}
}
console.log(cc('A'));
Example B
More Blackjack Rules Applied
/**
* #desc - Formats output to console
* #param {any} data - Limited like JSON
*/
const msg = data => console.log(JSON.stringify(data));
/**
* #desc - Given an unknown amount of numbers and
* strings that represent a set of 52 playing
* cards, it'll calculate:
* score points[0]
* hint points[1]
* aces points[2]
* #param {array<rest>} cards - One or more numbers and
strings.
* #return {array<number>} An array of 3 numbers
* see #desc
*/
const score = (...cards) => {
let tuple = [...cards].reduce((points, current) => {
points[0] += current === 'A' ? 11 : typeof current == 'string' ? 10 : current;
points[1] += current < 7 ? 1 : current > 9 ? -1 : 0;
return points;
}, [0, 0]);
let aces = [...cards].filter(c => c === 'A').length;
tuple.push(aces);
return tuple;
}
/**
* #desc - Given an array of 3 numbers, it calculates,
* adjusts, and determines the flow of a
* blackjack game
* #param {array<number>} score - The output of score()
* #return {string} A msg to inform the player of
* current score, a suggestion of what
* to do next, or when the player busts
*/
const hand = score => {
while (score[0] > 21) {
if (score[2] > 0) {
score[2] -= 1;
score[1] += 1;
score[0] -= 10;
} else {
return msg(`${score[0]}, bust!`);
}
}
if (score[0] === 21) {
return msg('BLACKJACK!');
}
let action = score[1] > 0 ? 'make a wager' : 'stand';
return msg(`${score[0]}, you should ${action}.`);
}
hand(score('A', 4, 10));
hand(score(2, 9, 3, 9));
hand(score(10, 'K'));
hand(score(6, 2));
hand(score('A', 'K'));
Example C
Wrong vs. Right
let count = 0;
let countA = 0;
let countB = 0;
let countC = 0;
function A(card) {
console.info('Test ' + count + ' ============================');
console.log('------------A👍-------------');
console.log('inA: ' + countA + ' Card: ' + card);
switch (card) {
case 2:
case 3:
case 4:
case 5:
case 6:
countA++; // 👍
console.log('A++: ' + countA);
break;
case 7:
case 8:
case 9:
countA; // 👍
console.log('A: ' + countA);
break;
case 10:
case "J":
case "Q":
case "K":
case "A":
countA--; // 👍
console.log('A--: ' + countA);
break;
}
if (countA > 0) {
return countA + " Bet";
} else {
return countA + " Hold";
}
}
function B(card) {
console.log('------------B⛔-------------')
console.log('inB: ' + countB + ' Card: ' + card);
switch (card) {
case 2:
case 3:
case 4:
case 5:
case 6:
console.log('B++: ' + countB);
return countB++; // ⛔
break;
case 7:
case 8:
case 9:
console.log('B: ' + countB);
return countB; // ⛔
break;
case 10:
case "J":
case "Q":
case "K":
case "A":
console.log('B--: ' + countB);
return countB--; // ⛔
break;
}
if (countB > 0) {
return countB + " Bet";
} else {
return countB + " Hold";
}
}
function C(card) {
console.log('------------C⚠️-------------');
console.log('inC: ' + countC + ' Card: ' + card);
switch (card) {
case 2:
case 3:
case 4:
case 5:
case 6:
console.log('--C: ' + countC + '⚠️');
return ++countC; // ⚠️
break;
case 7:
case 8:
case 9:
console.log('C: ' + countC);
return countC; // ⚠️
break;
case 10:
case "J":
case "Q":
case "K":
case "A":
console.log('--C: ' + countC + '⚠️');
return --countC; // ⚠️
break;
}
if (countC > 0) {
return countC + " Bet";
} else {
return countC + " Hold";
}
}
document.forms.bj.oninput = hit;
document.forms.bj.onreset = reset;
function hit(e) {
const IO = this.elements;
let draw = IO.draw.value;
let card = isNaN(draw) ? draw : +draw;
let a = A(card);
let b = B(card);
let c = C(card);
IO.A.value = a;
IO.B.value = b;
IO.C.value = c;
count++;
}
function reset(e) {
count = 0;
countA = 0;
countB = 0;
countC = 0;
}
html {
font: 300 1.5ch/1.2 'Segoe UI';
}
#table {
display: flex;
max-width: max-content;
}
legend {
font-size: 1.25rem;
}
select,
input {
display: inline-flex;
font: inherit;
}
label,
select,
input,
output {
display: block;
margin-bottom: 0.5rem;
}
output {
font-weight: 900;
text-align: center;
}
#B {
color: red;
}
.as-console-row::after {
width: 0;
font-size: 0;
}
.as-console-row-code {
width: 100%;
word-break: break-word;
}
.as-console-wrapper {
min-height: 100% !important;
max-width: 50%;
margin-left: 50%;
}
<form id='bj'>
<fieldset id='table'>
<legend>Blackjack</legend>
<label class='IN'>
<select id='draw'>
<option selected>Draw</option>
<option value='2'>2</option>
<option value='3'>3</option>
<option value='4'>4</option>
<option value='5'>5</option>
<option value='6'>6</option>
<option value='7'>7</option>
<option value='8'>8</option>
<option value='9'>9</option>
<option value='10'>10</option>
<option value='J'>Jack</option>
<option value='Q'>Queen</option>
<option value='K'>King</option>
<option value='A'>Ace</option>
</select>
<input type='reset'>
</label>
<fieldset>
<label>
Original: 👍<br>
<output id='A'> </output>
</label>
<label>
Short Circuits: ⛔<br>
<output id='B'> </output>
</label>
<label>
Short Circuits<br>Prefixed Operators: ⚠️<br>
<output id='C'> </output>
</label>
</fieldset>
</fieldset>
</form>
I can't implement the function tipPercentage that takes the argument rating as a string and return the values:
terrible or poor, then returns 3
good or great, then returns 10
excellent, returns 20
none of the above, returns 0
the input format for custom testing must be that the first line contains a integer, n, denoting the value of rating
HELP FOR A BEGGINNER!!!
You can use a switch statement to do this relatively easily, we check the input rating, then return the relevant tip percentage.
If we don't have a tip percentage for the rating, we'll fall back to the default condition, and return 0.
One could also use a map, though a switch statement is probably more flexible.
// Takes rating as a string and return the tip percentage as an integer.
function tipPercentage(rating) {
switch ((rating + "").toLowerCase()) {
case "terrible":
case "poor":
return 3;
case "good":
case "great":
return 10;
case "excellent":
return 20;
default:
return 0;
}
}
let ratings = ["excellent", "good", "great", "poor", "terrible", "meh", null];
for(let rating of ratings) {
console.log(`tipPercentage(${rating}):`, tipPercentage(rating))
}
function tipPercentage(rating) {
switch(rating) {
case "terrible":
case "poor":
return 3; break;
case "good":
case "great":
return 10; break;
case "excellent":
return 20; break;
default:
return 0;
}
}
Instead of a switch statement you could simply use an object:
const tipPercentage = {
'excellent': 20,
'good': 10,
'great': 10,
'terrible': 3,
'poor': 3,
}
const myRatings = [
"excellent",
"good",
"great",
"terrible",
"whatever",
undefined
];
for (let rating of myRatings) {
// Tip percentage, or 0 if it doesn't exist
console.log(rating, tipPercentage[rating] || 0)
}
I'm working on a Word Search puzzle game, and I'm struggling with generating the grid with letters. At the moment, I'm able to generate the grid, but the performance is very slow (way too slow for a decent usage, it can take up to 30-40 seconds before generating the grid).
To fit words into the grid, I'm using a recursive function that tries to fit the last word from a given list, then the one before it, etc, and if it doesn't fit, backtracks to the previous word and changes its place, then tries again. I guess I'm not doing this right, because it keeps going back and forth.
I tried to use weighed probabilities for directions, so that the generating is made in a "smarter" way, but I'm not getting any results yet.
My question :
How can I optimize this code to make it more performant and reliable? I accept any suggestions, even if it makes me do a lot of changes (if there is an iterative solution for example instead of a recursive one, or if I'm not reasoning correctly in the function...).
Here is the function:
function tryWord(grid, wordList, index, gridLength){
var valid = false;
var clear = false;
if(index==(wordList.length-1)){
/* Clear grid for current index */
for(var j=0; j<gridLength.x; j++){
grid[index][j] = [];
for (var k=0; k<gridLength.y; k++){
grid[index][j][k] = '';
}
}
var nbIterations = 0;
// Try current word
while(valid == false){
// Impossible to resolve this grid
if(nbIterations>500){
return false;
}
nbIterations++;
var initX = Math.floor(Math.random() * gridLength.x); // x coord of first letter of the word
var initY = Math.floor(Math.random() * gridLength.y); // y coord of first letter of the word
var direction = Math.floor(Math.random() * 8); // direction of the word (0=top-left; 1=top; 2=top-right; 3=right; 4=bottom-right; 5=bottom; 6=bottom-left; 7=left)
valid = checkValidWord(wordList[index].length, initX, initY, direction, gridLength);
}
clear = checkClearWord(wordList[index], initX, initY, direction, grid[index]);
if(!clear){
return false;
}
var x = initX;
var y = initY;
for(var j=0; j<wordList[index].length; j++){
grid[index][x][y] = wordList[index].charAt(j);
switch(direction){
case 0:
x--;
y--;
break;
case 1:
y--;
break;
case 2:
x++;
y--;
break;
case 3:
x++;
break;
case 4:
x++;
y++;
break;
case 5:
y++;
break;
case 6:
x--;
y++;
break;
case 7:
x--;
break;
default:
break;
}
}
return grid;
}
else if(index!=(wordList.length-1)){
var emptyGrid = true;
for(var p=0; p<grid[index].length; p++){
for(var q=0; q<grid[index][p].length; q++){
if(grid[index][p][q]!=''){
emptyGrid = false;
}
}
}
if(emptyGrid || $scope.nbIterations>50){
$scope.nbIterations=0;
grid = tryWord(grid, wordList, index+1, gridLength);
}
/* Prepare grid for current index */
grid[index] = grid[index+1];
if(grid!=false){
// Try current word
while(valid == false){
var initX = Math.floor(Math.random() * gridLength.x); // x coord of first letter of the word
var initY = Math.floor(Math.random() * gridLength.y); // y coord of first letter of the word
var direction = Math.floor(Math.random() * 8); // direction of the word (0=top-left; 1=top; 2=top-right; 3=right; 4=bottom-right; 5=bottom; 6=bottom-left; 7=left)
valid = checkValidWord(wordList[index].length, initX, initY, direction, gridLength); // Check that word fits in the grid
}
clear = checkClearWord(wordList[index], initX, initY, direction, grid[index]);
// If word is obstructed by other words
if(!clear){
$scope.nbIterations++;
return tryWord(grid, wordList, index, gridLength); // Try again
}
else{
var x = initX;
var y = initY;
for(var j=0; j<wordList[index].length; j++){
grid[index][x][y] = wordList[index].charAt(j);
switch(direction){
case 0:
x--;
y--;
break;
case 1:
y--;
break;
case 2:
x++;
y--;
break;
case 3:
x++;
break;
case 4:
x++;
y++;
break;
case 5:
y++;
break;
case 6:
x--;
y++;
break;
case 7:
x--;
break;
default:
break;
}
}
return grid;
}
}
else{
return false;
}
}
}
Parameters of the function :
grid : at the beginning, just a 10x11x11 array filled with '' string.
wordList : an array of words to put in the grid
index : 0 when calling the function for the first time, then it's used to check how deep we are in the wordList
gridLength : an array : {x:11, y:11}, giving the grid length
Some more precisions about the function :
The function checkValidWord checks if a given word going to a given direction fits in a grid with a given size. Returns true or false.
The function checkClearWord checks if a given word going to a given direction fits in the grid with other words already in it (no obstruction etc). Returns true or false.
The function tryWord is supposed to output a 3-dimensional array of size [wordList.length;11;11]. I then use the grid[0] as my 2 dimensional grid for the game.
Okay so I was trying to think of a good example, and so I created a constructor function about precious metals. The constructor takes in type of metal and weight. I have two methods. One method determines if the precious metal (gold or silver) is real and the other calculates the value based on spot price. (I know the spot price is wrong, this is just an example anyway).
Suppose a customer brought in a silver piece that is 80% silver. Because its 80% silver I want to apply that to my metalValue method. How would I do that.
Here is the code. (JSFiddle provided for your convience http://jsfiddle.net/bwj3fv12/).
This will help me understand constructors better.
HTML
<div id="testDiv">test Div</div>
<div id="testDiv2">test Div2</div> <br /><br />
JavaScript
var PreciousMetals = function(metal, weight){
this.metal = metal;
this.weight = weight; //weight in ounces
this.authentic = function(colorTest){
var metalPurity;
var zero = "";
if (this.metal == "silver"){
switch(colorTest){
case "brightred":
metalPurity = 1;
break;
case "darkred":
metalPurity = 0.925;
break;
case "brown":
metalPurity = 0.80;
break;
case "green":
metalPurity = 0.50;
break;
default:
metalPurity = 0;
}
}else if(this.metal == "gold"){
switch(colorTest){
case "green":
metalPurity = "base metal or gold plated";
break;
case "milk colored":
metalPurity = "gold plated sterling silver";
break;
case "no color":
metalPurity = "real gold";
break;
default:
metalPurity = "Could be a fake, try different test";
}
}
return metalPurity;
}
this.metalValue = function(metal){
var sum = 0;
var spotPrice;
if (this.metal == "gold"){
spotPrice = 1000;
}else if(this.metal == "silver"){
spotPrice = 15;
}
sum = spotPrice * this.weight;
return sum;
}
}
var customerCindy = new PreciousMetals("silver", 2);
document.getElementById('testDiv').innerHTML = customerCindy.authentic("brown");
document.getElementById('testDiv2').innerHTML = customerCindy.metalValue(); //The result I would like would be 24 of course.
Now I realize I could do it this way:
document.getElementById('testDiv2').innerHTML = customerCindy.metalValue() * customerCindy.authentic("brown");
However the goal here is to take in the information from the authentic method and use that to help me calculate the metal Value in the metalValue method.
If you'd like to keep the logic of these two methods separate in your constructor function, you could include a third method that performs the task of multiplying the two results.
var PreciousMetals = function(metal, weight){
this.metal = metal;
this.weight = weight; //weight in ounces
this.authentic = function(colorTest){
var metalPurity;
var zero = "";
if (this.metal == "silver"){
switch(colorTest){
case "brightred":
metalPurity = 1;
break;
case "darkred":
metalPurity = 0.925;
break;
case "brown":
metalPurity = 0.80;
break;
case "green":
metalPurity = 0.50;
break;
default:
metalPurity = 0;
}
}else if(this.metal == "gold"){
switch(colorTest){
case "green":
metalPurity = "base metal or gold plated";
break;
case "milk colored":
metalPurity = "gold plated sterling silver";
break;
case "no color":
metalPurity = "real gold";
break;
default:
metalPurity = "Could be a fake, try different test";
}
}
return metalPurity;
}
this.metalValue = function(){
var sum = 0;
var spotPrice;
if (this.metal == "gold"){
spotPrice = 1000;
}else if(this.metal == "silver"){
spotPrice = 15;
}
sum = spotPrice * this.weight;
return sum;
}
this.netValue = function(colorTest){
return this.authentic(colorTest) * this.metalValue();
}
}
Here's a working JSFiddle - https://jsfiddle.net/bwj3fv12/
If you wanted to update metalValue to include the results from the purity check, you could just update
this.metalValue = function(colorTest){
// ...
sum = spotPrice * this.weight * this.authentic(colorTest);
return sum;
}
and call it with
customerCindy.metalValue('brown');
Since this is just an example, there's no reason to worry about it, but presumably the purity in the real world would be just as much an instance property as the metal and the weight, and wouldn't actually be a transient value for a method like this. But that's neither here nor there.
I'm currently working on an idle game and have encountered a problem.
I'm trying to create a function that will lessen the value of 1 of 6 variables (each represents a type of worker) a number of times. However, if one of these 6 variables is at 0, I need it to pick one of the other 5, and I can't get that to work.
With fiddling around, I have gotten this:
function killWorker(amount){
var job;
for (var i=0;i<amount;i++){
job = Math.floor((Math.random()*6)+1);
switch (job){
case 1:
if(unass_workers>0){
unass_workers -= 1;
}else{
i-=1;
}
case 2:
if(farm_workers>0){
farm_workers -= 1;
}else{
i-=1;
}
break;
case 3:
if(tree_workers>0){
tree_workers -= 1;
}else{
i-=1;
}
break;
case 4:
if(metMine_workers>0){
metMine_workers -= 1;
}else{
i-=1;
}
break;
case 5:
if(golMine_workers>0){
golMine_workers -= 1;
}else{
i-=1;
}
break;
case 6:
if(paper_workers>0){
paper_workers -= 1;
}else{
i-=1;
}
break;
default:
console.log("error: killWorker() didn't work properly");
break;
}
}
}
This worked when I did low amounts, but when I increased the amount higher the whole thing crashed. I'd be quite happy to drastically change the function if it would work better, and I am using jquery too if that could help get an easier or more effective solution.
As i said, what probably happens is that amount is larger than the sum of X_workers, resulting in an infinite loop.
A fix that keeps your logic correct would be to check that enough workers can be killed:
function killWorker(amount){
if (amount < unass_workers + farm_workers + ...) return "Genocide";
var job;
for (var i=0;i<amount;i++){
...
}
A better way to organize your data structures could be:
var workerNames = [ 'unass', 'farm', 'tree', 'metMine', 'golMine', 'paper' ];
var workers = {
unass : 23,
farm : 45,
tree : 6,
metMine : 99,
golMine : 3,
paper: 43
}
function getRandomLiveJobName() {
var jobs = workerNames.filter(function(name) {
return workers[name] > 0;
});
return jobs[Math.floor(Math.random()*jobs.length)];
}
function killWorkers(amount) {
for (;amount--;) {
var job = getRandomLiveJobName();
if (!job) return "Genocide";
workers[job]--;
}
}
killWorkers(150);
console.log(workers); // { farm: 0, golMine: 0, metMine: 64, paper: 5, tree: 0, unass: 0 }
Of course it could be optimized by not creating a new array each time you need a random live job by updating a single array inside killWorkers, but i think it's easier to read now.