So, I'm picking up JavaScript since I just seems to enjoy it. I bought an online course, and now facing a challenge in my code. It's a pretty simple one, so hopefully one of the experts out there can help the poor fella (me).
I have the following code, which to my knowledge, should be proper:
var johnAge = 25;
var markAge = 30;
var steveAge = 40;
var johnHeight = 170;
var markHeight = 175;
var steveHeight = 150;
var John, Mark, Steve;
John = johnHeight + 5 * johnAge;
Mark = markHeight + 5 * markAge;
Steve = steveHeight + 5 * steveAge;
console.log('John: ' + John);
console.log('Mark: ' + Mark);
console.log('Steve: ' + Steve);
if (John > Mark && Steve) {
console.log('John wins!');
} else if (Mark > Steve && John) {
console.log('Mark wins!');
} else if (Steve > John && Mark) {
console.log('Steve wins!');
} else {
console.log("it's a draw.");
}
Here is my problem:
John: 295
Mark: 325
Steve: 350
Steve wins!
Now, this game is about having the one with the highest points to win the game. Steve is obviously the winner based on his score.
The problem is with this part right here:
} else if (Mark > Steve && John) {
console.log('Mark wins!');
} else if (Steve > John && Mark) {
console.log('Steve wins!');
It will show that 'Mark' won the game if and when i make the following changes:
} else if (Mark > John && Steve) {
console.log('Mark wins!');
}
I simply just switched locations between 'John' and 'Steve'. If John comes first, it shows that 'Mark won the game', and if Steve comes firs, it moves on and executes the next 'else if' which is 'Steve won the game'.
What I don't understand is why changing positions between my variables causing such huge difference even though I'm using the logical and &&.
This problem doesn't seem to exist when I use binary and &. Though, as far as I've read about this, binary and isn't preferable in coding and doesn't have much real applications where it can be used.
So if possible, I'd like to fix this my using && instead of relying on &.
I really don't understand the changes and their cause.
Thank you in advance for helping and guiding me.
~Cheers
You need to check the player against both of the others like this:
The way you have it now your if statement is Steve > John(true) && Mark(true).
var johnAge = 25;
var markAge = 30;
var steveAge = 40;
var johnHeight = 170;
var markHeight = 175;
var steveHeight = 150;
var John, Mark, Steve;
John = johnHeight + 5 * johnAge;
Mark = markHeight + 5 * markAge;
Steve = steveHeight + 5 * steveAge;
console.log('John: ' + John);
console.log('Mark: ' + Mark);
console.log('Steve: ' + Steve);
if (John > Mark && John > Steve ) {
console.log('John wins!');
} else if (Mark > Steve && Mark > John) {
console.log('Mark wins!');
} else if (Steve > John && Steve > Mark) {
console.log('Steve wins!');
} else {
console.log("it's a draw.");
}
You can do this, you just have to make an improved > operator that suits your needs. But please stay tuned until the end of this answer. We'll address some other things that we really need to talk about
const gtAll = (x, y, ...ys) =>
ys.length === 0
? x > y
: x > y && gtAll (x, ...ys)
console.log ('5 is greater than 4, 3, and 2', gtAll (5, 4, 3, 2))
// true
console.log ('5 is greater than 4, 3, and 7', gtAll (5, 4, 3, 7))
// false
In your program you could use it like below.
if (gtAll (John, Mark, Steve))
console.log ("John wins")
else if (gtAll (Mark, Steve, John))
console.log ("Mark wins")
else if (gtAll (Steve, John, Mark))
console.log ("Steven wins")
else
console.log ("draw")
But if we zoom out a little bit, there's a bigger problem to solve with your program. Let's look at some of the things that are sticking out.
Have a bunch of vars named fooColor, fooSize, fooAdjective? You're probably looking for an Object.
var johnAge = 25
var johnHeight = 170
var john = { name: 'John', age: 25, height: 170 }
Assigning multiple vars for values of the same kind ie John, Mark, Steve? You're probably looking for an Array.
var John = { ... }
var Mark = { ... }
var Steve = { ... }
const people =
[ { name: 'John', age: 25, height: 170 }
, { name: 'Mark', age: 30, height: 175 }
, { name: 'Steve', age: 40, height: 150 }
]
Does it feel like you're starting to repeat yourself? Use functions to perform repeated tasks
John = johnHeight + 5 * johnAge;
Mark = markHeight + 5 * markAge;
Steve = steveHeight + 5 * steveHeight;
const score = p =>
p.height + 5 * p.age
console.log (score (people [0])) // John
// 295
console.log (score (people [1])) // Mark
// 325
console.log (score (people [2])) // Steve
// 350
console.log (people.map (score))
// [ 295, 325, 350 ]
Here's a rewrite of your program. Most importantly notice that we didn't even end up using gtAll as because there's a better way to think about the problem now that our people are represented as objects in an array. And remember to use functions where work is repeated!
const people =
[ { name: 'John', age: 25, height: 170 }
, { name: 'Mark', age: 30, height: 175 }
, { name: 'Steve', age: 40, height: 150 }
]
const score = (p) =>
p.height + 5 * p.age
const determineWinner = (contested, challenger) =>
{
if (score (challenger) > score (contested))
return challenger
else if (score (challenger) === score (contested))
return { ...contested, name: "tie" }
else
return contested
}
const winner =
people.reduce (determineWinner)
console.log ("the winner is", winner)
// the winner is { name: 'Steve', age: 40, height: 150 }
console.log ("the winner is", winner.name)
// the winner is Steve
Critically, notice that we can easily add another person to people and our program doesn't have to change in order to determine a winner. For example, if we added Charlie
var charlieAge = 60
var charlieHeight = 180
var Charlie = charlieHeight + 5 * charlieAge
Then we would have to go and alter all of the if statements. You can see how this simply does not scale
if (gtAll (John, Mark, Steve))
if (gtAll (John, Mark, Steve, Charlie))
console.log ("John wins")
else if (gtAll (Mark, Steve, John))
else if (gtAll (Mark, Steve, John, Charlie))
console.log ("Mark wins")
else if (gtAll (Steve, John, Mark))
else if (gtAll (Steve, John, Mark))
console.log ("Steven wins")
else if (gtAll (Charlie, John, Mark, Steve))
console.log ("Charlie wins")
else
console.log ("draw")
Compare that to simply adding Charlie to our people array
const people =
[ { name: 'John', age: 25, height: 170 }
, { name: 'Mark', age: 30, height: 175 }
, { name: 'Steve', age: 40, height: 150 }
, { name: 'Charlie', age: 60, height: 180 }
]
const winner =
people.reduce (determineWinner)
console.log (winner.name)
// Charlie
References for this answer
Object
Array
Array.prototype.map
Array.prototype.reduce
Rest parameter
Spread syntax
You want to use comparisons on both sides of the && operator:
if(Mark > Steve && Mark > John)
The && operator treats its operands as booleans. The expression if(Mark > Steve && John) means
if both are true:
Mark is greater than Steve, and
John has a true value when John is coerced to a boolean (namely, for a number, when John is nonzero)
This is obviously not what you wanted to test for.
Related
As a complete beginner, I'm trying to understand the outcome of this code. What confuses me is:
if (minAge > people[i].age) {
minAge = people[i].age;
youngestPersonsName = people[i].name;
How I'm understanding this, is that if John:31 is greater than Joseph:30 (True statement), then John:31 is assigned to minAge ??
In my head, John is the outcome due to his age > than the rest!
var people = [
{
name: "John",
age: 31
},
{
name: "Joseph",
age: 30
},
{
name: "Mary",
age: 19
}
];
var youngestPersonsName = people[0].name;
var minAge = people[0].age;
for (var i = 0; i < people.length; i++) {
if (minAge > people[i].age) {
minAge = people[i].age;
youngestPersonsName = people[i].name;
}
}
console.log("The youngest person is" + youngestPersonsName);
the algorithm just loops over all the entries, checking if the global minAge is greater than the current item of the loop (meaning that the looped item is younger than the global minAge). if this is the case the minAge is replaced by that element... at the end of the loop Mary should be the outcome. In your case the loop starting at index 0 is of no use since you already assigned it as youngest. before the loop.
I need to use plain JavaScript to convert an amount of experience points to an amount of hours played by a number of fixed rates.
For example:
A player has 1,129,518 experience points.
The amount of experience points that are gained per hour depends on the amount of xp one already has. They would be arranged something like this:
above 0 xp: 8,000 xp/h
above 2,107 xp: 20,000 xp/h
above 101,333 xp: 45,000 xp/h
above 1,210,421 xp: 68,500 xp/h
above 13,034,431 xp: 75,000 xp/h
I'm struggling to find a way to use these xp rates to convert a given amount of experience points to hours played, using at least somewhat elegant Javascript.
I just end up with a cunfusing mess of if/else statements that ends up failing because of math errors.
Any Math wizards out there that can help me? Thanks.
Code Sample: I would go from here
if(xp === 0){
return 0;
}else if( 2107 >= xp > 0){
const result = (xp/8000).toFixed(1);
return result;
}else if(101333 >= xp > 2107){
const result = ((2107/8000) + ((xp-2107)/20000)).toFixed(1);
return result;
}else if(1210421 >= xp > 101333){
...
}
As you can see it would quickly get out of hand if theres alot of different tiers.
First of all, you should write your if statements like this:
if( 2107 >= xp && xp > 0){
...
}
Next, try thinking about XP as buckets of XP and each bucket having different value/price. Go from most valuable bucket to least valuable, and for each bucket calculate hours and subtract amount of XP that was used to calculate those hours.
You can do this in while loop:
let hours = 0;
while(XP > 0)
{
// figure out bucket you are in, your if statements are fine for that.
let value = 0;
let lowerBoundary = 0;
if( 101333 >= xp && xp > 2107){
value = 20000;
lowerBoundary = 2107;
// you need lower boundary to figure out how many XP you used in this bucket.
}
// else if...
const usedInBucket = XP - lowerBoundary;
hours += usedInBucket / value; // simply calculate hours needed
XP -= usedInBucket;
}
This is what I came up with:
const steps = [{
min: 0,
val: 8000
},
{
min: 2107,
val: 20000
},
{
min: 101333,
val: 45000
},
{
min: 1210421,
val: 68500
},
{
min: 13034431,
val: 75000
},
].sort((a, b) => b.min - a.min);
//using for loop
function xpToHours(xp = 0) {
let h = 0;
steps.forEach(s => {
let amt = Math.max(xp - s.min, 0);
h += amt * s.val;
xp -= amt;
});
return h;
}
//using reduce
function xpToHours2(xp = 0) {
return steps.reduce((h, s) => {
let amt = Math.max(xp - s.min, 0);
xp -= amt;
return h + amt * s.val;
}, 0)
}
[0, 1000, 2000, 3000, 1000000].forEach(xp => console.log(xp, xpToHours(xp)));
[0, 1000, 2000, 3000, 1000000].forEach(xp => console.log(xp, xpToHours2(xp)));
To explain:
steps is just an array containing your different stages. It is sorted by the minimum xp from highest to lowest.
Then we just iterate over this array calculating amt which is the xp used up by the currently highest stage. The needed time is therefore amt * currentstep.val and the xp is reduced by the calculated amount for the next stage.
The easiest way to do this is with a sorted array of ranges and Array.prototype.find
// Make sure this is sorted desc
const expRanges = [{
above: 101333,
xph: 45000
},
{
above: 2107,
xph: 20000
},
{
above: 0,
xph: 8000
}
];
function findExpPerHour(xp) {
return expRanges.find(range => range.above < xp).xph;
}
// TESTS
const playerExpTests = [{
name: "P1",
xp: 12
}, {
name: "P2",
xp: 12000
}, {
name: "P3",
xp: 200000
}, {
name: "P4",
xp: 99999999
}];
playerExpTests.forEach(p => {
console.log(p.name, "Exp per hour:", findExpPerHour(p.xp));
});
I was trying to figure it out how can I extend and then sort the items by the created extension variable.
if(score === 'Overall Score'){
// let midtermSort = _.sortBy(overAll, 'overall_score');
_.each(students, function(elem) {
_.extend(elem, {overall_score : (elem.midterm_score + elem.final_score) / 2});
_.sortBy(elem, 'overall_score');
console.log(elem.firstname + " " + elem.overall_score);
});
}
As you can see on my code, I iterate to the students and then extend a new column w/c is overall_score. So right now I need to sort the items via overall_score.
Here's what I got:
As you can see the overall score does not SORT them properly. Anything i was doing wrong? Please help.
UPDATE Side Note:
I tried to mixed it up with each function and it works but it was a long process. Any idea how to refactor it a little bit?
if(score === 'Overall Score'){
let overAllScore = _.each(students, function(elem) {
_.extend(elem, {overall_score : (elem.midterm_score + elem.final_score) / 2});
});
let sorted = _.sortBy(overAllScore, 'overall_score');
_.each(sorted, function(elem) {
console.log(elem.firstname + " " + elem.final_score);
});
}
This will do what you want, assuming of course that you wanted them listed from lowest to highest overall score. Note that I've indented the code for readability:
var score = 'Overall Score';
var students = [
{
firstname: 'Frank',
midterm_score: 80,
final_score: 80
},
{
firstname: 'Julie',
midterm_score: 50,
final_score: 65
},
{
firstname: 'Eddie',
midterm_score: 100,
final_score: 73
},
{
firstname: 'Bill',
midterm_score: 60,
final_score: 67
}
];
if (score === 'Overall Score') {
_.each(
_.sortBy(
_.map(
students,
function(elem) {
return _.extend(elem, {overall_score : (elem.midterm_score + elem.final_score) / 2});
}
),
'overall_score'
),
function(elem) {
console.log(elem.firstname + " " + elem.overall_score);
}
);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
_.sortBy, according to the documentation, returns a sorted copy of the Array. This is unlike the standard Array.sort().
You need to keep this value:
elem = _.sortBy(elem, ...)
Or, if you want to keep the entire dataset, use map instead of each:
students = _.map(students, function(elem) {
...
return _.sortBy(elem, ...)
})
var players = [];
var totalplayers = 0
var team1 = [];
var team2 = [];
var commands = {
"teams.join": {
name: "Join random team.",
description: "Anybody who joins will be put to a team",
process: function(bot, msg, suffix) {
players.push(msg.sender);
bot.sendMessage(msg.channel, players);
bot.sendMessage(msg.channel, msg.sender + " has been added to the random team selection.");
totalplayers += 1;
bot.sendMessage(msg.channel, totalplayers)
},
},
"teams.random": {
name: "Random team selection.",
desciption: "Displays all players in random team selection in a random team.",
process: function(bot, msg, suffix) {
var playcount = 0;
bot.sendMessage(msg.channel, "tp: " + totalplayers); // Check
bot.sendMessage(msg.channel, "i: " + playcount); // Check
for (playcount = 0; playcount < totalplayers; playcount++) {
//bot.sendMessage(msg.channel, "Looping?") // Check
var Rteam = players[Math.floor(Math.random() * players.length)];
//bot.sendMessage(msg.channel, Rteam); // Check
if (playcount = 0 || 2 || 4 || 6 || 8) {
team1.push(Rteam);
bot.sendMessage(msg.channel, "isEven = true"); // Check
playcount + 1;
} else if (playcount = 1 || 3 || 5 || 7 || 9) {
team2.push(Rteam);
bot.sendMessage(msg.channel, "isEven = false"); // Check
playcount + 1;
}
playcount + 1;
var roll = players.splice(Rteam, 1);
var yourNumber = roll[totalplayers];
//i += 1;
}
bot.sendMessage(msg.channel, "Team 1: " + team1);
bot.sendMessage(msg.channel, "Team 2: " + team2);
},
}
teams.join works fine, but I included to show the whole section of the teams. teams.random should take the players array, randomise the players (users) locations in the array, then distribute them so that players[0] is on Team 1, players[1] is on Team 2 (etc.) until there are no more users left. Essentially it is taking a list and splitting it up into two groups randomly. The main thing I realized while testing this is that playcount doesn't increment (I've tried For, While and Do-Until loops to no avail.
This is not correct:
if (playcount = 0 || 2 || 4 || 6 || 8) {
There are two problems: First, you're using =, which is assignment, when it should be == for comparison. Second, you can't use || that way to compare against multiple elements, you need to do the comparisons separately for each item. So it should be:
if (playcount == 0 || playcount == 2 || playcount == 4 || playcount == 6 || playcount == 8) {
But if you want to know if playcount is even, you can just do:
if (playcount % 2 == 0)
You don't need to use else if for the other test, just use else, since there are just two possibilities.
The lines that contain:
playcount + 1;
don't do anything. You don't need this at all, since you're incrementing playcount in the for() header.
By the way, there's nothing in your code that prevents you from selecting the same player multiple times, and adding them to different teams.
I agree with barmar on his comments about the OP original code.
But for completeness sake, here's a sample code to split the teams in half and randomly place players in each team.
var players = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
team1 = [],
team2 = [],
i;
while (players.length > 0) {
i = Math.floor(Math.random() * players.length);
if (players.length % 2 === 0) {
team1.push(players[i]);
} else {
team2.push(players[i]);
}
players.splice(i, 1);
}
console.log(team1, team2);
Output:
[10, 4, 1, 6, 5] [3, 8, 2, 9, 7]
Run the code in browser console a few times, you'll get different random numbers in each array.
I have a javascript "object." Im using the word object to make things easier. Which is here:
var character = {
name: "",
myClass: "",
health: 20,
maxHealth: 20,
};
Say i have a game, and the game has fights, and after each fight you gain a health point. which is done with:
character.maxHealth += 1;
However... When i tried to do this, i ended up getting 201 as the maxHealth or 2032 or 203232 or whatever number i wanted to add to the max health was just adding as if it was a string. through my eyes in looks like an integer to me but i must be mistaken. if anyone can give me a hand it would be really appreciated. That is an example of what i have. the actual code is:
var character = {
name: "",
myClass: "",
health: 20,
maxHealth: 20,
stamina: 10,
maxStamina: 10,
mana: 5,
maxMana: 5,
physStrength: 3,
minAttack: 0,
mentStrength: 3,
physDefense: 3,
mentDefense: 3,
exp: 0,
punch: function() {
toggleAttackButtons(0);
this.minAttack = this.physStrength/3;
var damage = Math.floor(Math.random() * this.physStrength) + this.minAttack;
addString("You punched and did " + damage + " damage.");
myEnemy.health -= damage;
updateStats();
setTimeout(function(){
myEnemy.attack();
toggleAttackButtons(1);
updateStats();
}, 1000);
},
kick: function(){
toggleAttackButtons(0);
this.minAttack = this.physStrength/3;
var damage = Math.floor(Math.random() * this.physStrength) + this.minAttack;
addString("You kicked and did " + damage + " damage.");
myEnemy.health -= damage
updateStats();
setTimeout(function(){
myEnemy.attack();
toggleAttackButtons(1);
updateStats();
}, 1000);
},
};
and this is where im incrementing the number:
var updateStats = function() {
document.getElementById("charHealth").innerHTML = "Health: " + character.health + " / " + character.maxHealth;
document.getElementById("enemHealth").innerHTML = "Health: " + myEnemy.health + " / " + myEnemy.maxHealth;
if(myEnemy.health <= 0){
myEnemy.health = 0;
character.maxHealth += 1;
removeFightScreen(1);
}
if(character.health <= 0){
removeFightScreen(2);
}
};
I understand the object is messy i plan on rewriting it in the future to be a lot more efficient. im just roughing it up right now.
I found myself the answer. Im sorry for my lack of code evidence, however it contains cookies, so when i write the cookies using the character stats like health and maxHealth, they end up being converted into strings to fit the cookie. Therefore i need to convert them back to integers. thank you guys for your help. Next time i will add in all the code i just felt that being there was several hundred lines of code i didnt want to go through it all.