Can't find where the error is in this JavaScript code. Not very familliar with 'class' syntax - javascript

This is the code. It is an assignment from a random stranger that asked me to solve it for him since he saw a post of mine on IG.
// Player class
class Player {
constructor(name, strength = 2, weapons) {
this.name = name;
this.health = 10;
this.strength = strength;
this.weapons = [...weapons];
}
applyDamage(int) {
this.health -= int;
}
isAlive() {
return this.health > 0;
}
attackWith() {
let randomNum = Math.floor(Math.random() * 8);
return this.weapons[randomNum];
}
}
// Weapon class
class Weapon {
constructor(name) {
this.name = name;
this.damage = Math.ceil(Math.random() * 5);
}
attack(player, enemy) {
if (player.isAlive() && player.isAlive()) {
let dmg = player.strength * this.damage;
enemy.applyDamage(dmg);
if (!enemy.isAlive()) {
return;
} else {
enemy.attack(player);
}
}
}
}
// Enemy class
class Enemy {
constructor(name = "Enemy", health = 5, strength = 2) {
this.name = name;
this.health = health;
this.strength = strength;
}
applyDamage(int) {
this.health -= int;
}
isAlive() {
return this.health > 0;
}
attack(player) {
player.applyDamage(this.strength);
}
}
// BattleSimulation class
class BattleSimulation {
constructor() {
this.players = [];
this.enemies = [];
}
createEnemies() {
for (let i = 0; i < 20; i += 1) {
this.enemies[i] = new Enemy();
}
}
createPlayers() {
// Weapons
let pencil = new Weapon("Pencil");
let book = new Weapon("Book");
let screwdriver = new Weapon("Screwdriver");
let theOneRing = new Weapon("Sauron's Ring");
let mustardGass = new Weapon("Mustard Gass");
let bigBoy = new Weapon("A Nuke");
let love = new Weapon("Power of Love");
let theForce = new Weapon("The Force");
let weaponsCache = [
pencil,
book,
screwdriver,
theOneRing,
mustardGass,
bigBoy,
love,
theForce
];
// Players
let luke = new Player("Luke", 5, weaponsCache);
let baldingCoder = new Player("DraciVik", 10, weaponsCache);
let trump = new Player("Trump", 1, weaponsCache);
let kikiMakarena = new Player("Kiki Makarena", 5, weaponsCache);
let johnWick = new Player("John Wick", 2, weaponsCache);
this.players = [luke, baldingCoder, trump, kikiMakarena, johnWick];
}
run() {
console.log("Simulating Battle");
this.createEnemies();
this.createPlayers();
while (this.players.length !== 0 || this.enemies.length !== 0) {
let randomPlayerIndex = Math.floor(Math.random() * this.players.length);
let randomPlayer = this.players[randomPlayerIndex];
let randomEnemyIndex = Math.floor(Math.random() * this.enemies.length);
let randomEnemy = this.enemies[randomEnemyIndex];
let weapon = randomPlayer.attackWith();
weapon.attack(randomPlayer, randomEnemy);
if (!randomPlayer.isAlive()) {
this.players.splice(randomPlayerIndex, 1);
}
if (!randomEnemy.isAlive()) {
this.enemies.splice(randomEnemyIndex, 1);
}
}
console.log(this.players);
if (this.players.length > 0) {
return "Congratulations, you have defeated Scarlet Byle";
}
return "Sorry, Scarlet Byle has defeated you and conquered the free world";
}
}
let battle = new BattleSimulation();
battle.run();
Anyone can see where the error is? I get a return error 'enemy.applyDamage(dmg)' is undefined.
What is this error that I need more writing than just code? Should I spam some letters?

The bug here is actually just in the condition of your while loop:
while(this.players.length !== 0 || this.enemies.length !== 0)
Your condition says to loop while there is at least one player OR there is at least one enemy. So as long as ONE of the arrays is not empty, it will continue to loop.
But when you first create this.players and this.enemies, they start at different sizes. Then when you remove one entry from each array, eventually one of the arrays is empty before the other.
Then your code has var randomEnemyIndex = Math.floor(Math.random() * this.enemies.length); which will evaluate to 0 when the array is empty. And when you do this.enemies[0], it returns undefined. When undefined is passed into weapon.attack like weapon.attack(randomPlayer, undefined), then it tries to call applyDamage(dmg) on the undefined, which throws your exception.
If you modify your code to have the following console logs, you will see the issue:
while (this.players.length !== 0 || this.enemies.length !== 0) {
console.log("PLAYERS: " + this.players.length.toString());
console.log("ENEMIES: " + this.enemies.length.toString());
...
You will see:
'Simulating Battle'
'PLAYERS: 5'
'ENEMIES: 20'
'PLAYERS: 5'
'ENEMIES: 20'
'PLAYERS: 5'
[...]
'PLAYERS: 4'
'ENEMIES: 1'
'PLAYERS: 4'
'ENEMIES: 0'
error: Uncaught TypeError: Cannot read property 'applyDamage' of undefined
So to fix this, you'll need to either change your conditional to this:
while (this.players.length !== 0 && this.enemies.length !== 0) {
Or you'll need to start both arrays off at the same size.

Related

JavaScript Need help writing shouldQuarantine prototype

Need this to determine if any of the passengers are isHealthy =false then quarantine the wagon. I may have an issue on the join prototype as well. The isHealthy is only triggered if they eat and have no food. So it is possible for them to eat, and then have no food but not trigger isHealthy.
I am very new to this please be patient.
const Traveler = function (travelerName) {
this.name = travelerName;
this.food = 1;
this.isHealthy = true;
};
Traveler.prototype.hunt = function () {
this.food += 2;
console.log(this.food);
};
Traveler.prototype.eat = function () {
this.food -= 1;
if (this.food === 1) {
} else {
this.food === 0;
this.isHealthy = false;
}
console.log(this.food);
};
console.log(new Traveler("John"));
function Wagon(capacity) {
this.capacity = capacity;
this.passengers = [];
}
console.log(new Wagon(4));
Wagon.prototype.getAvailableSeatCount = function () {
let seatingCapacity = this.capacity - this.passengers.length;
console.log(seatingCapacity);
return seatingCapacity;
};
Wagon.prototype.join = function (traveler) {
console.log(this.capacity);
let currentCapacity = this.capacity;
if (currentCapacity <= this.passengers.length) {
this.currentCapacity = 0;
} else if (this.getAvailableSeatCount != 0) {
this.passengers.push(traveler);
}
console.log(this.passengers);
};
Wagon.prototype.shouldQuarantine = function () {
for (let i = 0; i < this.passengers.length; i++) {
if (this.passengers[i].isHealthy) {
return false;
}
}
};
Wagon.prototype.totalFood = function () {
let totalFood = "";
this.passengers.forEach(this.food);
console.log(this.food);
};
In you eat method of the Traveler class, first check if there is any food. If there is, then subtract one and check if the food is now empty. If it is then set isHealthy to false.
Traveler.prototype.eat = function () {
if (this.food > 0) {
this.food -= 1;
if (this.food === 0) {
this.isHealthy = false;
}
}
};
Subsequently you should also modify your hunt method to make your traveler healthy again after hunting.
Traveler.prototype.hunt = function () {
this.food += 2;
this.isHealthy = true;
};
In the shouldQuarantine method of the Wagon class, instead of checking if all passengers are healthy, check if anyone of them is not healthy and return true if that is the case.
If everyone is healthy, the loop will finish. Return false after the loop.
Wagon.prototype.shouldQuarantine = function () {
for (let i = 0; i < this.passengers.length; i++) {
if (!this.passengers[i].isHealthy) {
return true;
}
}
return false;
};
Alternatively you could use the some method on the this.passengers array to check if any of the passenger isn't healthy.
Wagon.prototype.shouldQuarantine = function () {
return this.passengers.some(
passenger => passenger.isHealthy === false
);
};
The join method can be simplyfied. You only need the result from this.getAvailableSeatCount() to see if there is any room. Add the traveler if the result is not 0.
Wagon.prototype.join = function (traveler) {
const availableSeats = this.getAvailableSeatCount();
if (availableSeats !== 0) {
this.passengers.push(traveler);
}
};
I also noticed that the totalFood method doesn't work as expected, but I'll let this one up to you. Hint: totalFood should be a number. Loop over every passenger and add the amount of food to the totalFood value.

I'm trying to create an array from ES class arguments but I'm getting an empty Array, why?

Consider this code for this scenario of creating an Array with value of "sideLength" and for "sides" times within this ES class, but I'm keep getting an empty array!! here is codepen link
class ShapeNew {
constructor(name, sides, sideLength) {
this.name = name;
this.sides = sides;
this.sideLength = sideLength;
}
tryArray() {
let sides_array = [];
for (let i = 0; i < this.sides; i++) {
sides_array = sides_array.push(this.sideLength);
}
return sides_array;
}
newPerimeter() {
let peri = this.tryArray();
console.log(peri.reduce((sum, accum) => sum + accum));
}
}
let new_square = new ShapeNew("square", 4, 5);
new_square.newPerimeter();
All I'm trying to do is convert 4 into [5,5,5,5], how do I do that?
Thanks in advance for looking into this, I appreciate it :)
You want this
sides_array.push(this.sideLength);
Not this
sides_array = sides_array.push(this.sideLength);
Because Array.push() does not return anything.
You are assigning to the variable sides_array the returned value of pushing a new element which is the new length, instead just push the element each time
class ShapeNew {
constructor(name, sides, sideLength) {
this.name = name;
this.sides = sides;
this.sideLength = sideLength;
}
tryArray() {
let sides_array = [];
for (let i = 0; i < this.sides; i++) {
sides_array.push(this.sideLength);
}
return sides_array;
}
newPerimeter() {
let peri = this.tryArray();
console.log(peri.reduce((sum, accum) => sum + accum));
}
}
let new_square = new ShapeNew("square", 4, 5);
new_square.newPerimeter();
But I was wondering, if all what you wanted to do is just calculating the perimeter, then why don't you just multiply the sides with side length?!
class ShapeNew {
constructor(name, sides, sideLength) {
this.name = name;
this.sides = sides;
this.sideLength = sideLength;
}
perimeter() {
return this.sideLength * this.sides;
}
}
let new_square = new ShapeNew("square", 4, 5);
console.log(new_square.perimeter());

Is there a way to keep a variable that a generate using Math.random when I run the function multiple times

I am trying to make a game when you have to guess a number that is generated by the Math.random() function in JavaScript. But I realized that when I do that I have to rerun the function if they get the number wrong. Then the number regenerates when it reruns the function. Is there a way that I can make the variable stay until I want to change it. I was going to change it using the const function but I realized it would do the same thing. Here is my full code:
var tries = 5;
var howMany = 0;
var wrong = 0;
var player1 = 0;
var player2 = 0;
var triesmulti = 10;
var turn = 'player 1';
var number;
function start() {
var min = document.getElementById('min').value;
var max = document.getElementById('max').value;
number = Math.floor(Math.random() * (+max - +min)) + +min;
if (tries < 1) {
alert('You \don\'t have any more tries left. The number was \n' + number);
tries = 5;
wrong += 1;
document.getElementById('wrong').innerHTML = 'You have got the number wrong ' + wrong + ' times';
} else {
var guess = prompt();
if (guess == number) {
alert('You got the number right!\n' + number);
howMany += 1;
tries = 5;
document.getElementById('howMany').innerHTML = 'You have guessed the number ' + howMany + ' times';
document.getElementById('tries').innerHTML = 'You have 5 tries left';
} else {
alert('You got the number wrong.');
tries -= 1;
document.getElementById('tries').innerHTML = 'You have ' + tries + ' tries left';
setTimeout(start, 1000);
}
}
}
function multiplayer() {
var min = document.getElementById('minm').value;
var max = document.getElementById('maxm').value;
number = Math.floor(Math.random() * (+max - +min)) + +min;
if (triesmulti < 1) {
alert('You \don\'t have any more tries left\n' + number);
triesmulti = 10;
document.getElementById('triesmulti').innerHTML = 'You have 5 tries for each player';
} else {
var guess = prompt(turn);
if (turn == 'player 1') {
if (guess == number) {
alert('You got the number right!\n' + number);
player1 += 1;
triesmulti = 10;
document.getElementById('triesmulti').innerHTML = 'You have 5 tries for each player';
} else {
alert('You got the number wrong!');
turn = 'player 2';
setTimeout(multiplayer, 1000);
}
} else if (turn == 'player 2') {
if (guess == number) {
alert('You got the number right!\n' + number);
player2 += 1;
triesmulti = 10;
document.getElementById('triesmulti').innerHTML = 'You have 5 tries for each player';
} else {
alert('You got the number wrong!');
turn = 'player1';
setTimeout(multiplayer, 1000);
}
}
}
}
If you see there, in the setTimeout() it reruns the function.
You can create a stateful random number generator quite easily with an object or closure:
const rndRng = (lo, hi) => ~~(Math.random() * (hi - lo) + lo);
const intRng = (lo, hi) => {
let n = rndRng(lo, hi);
return {
next: () => (n = rndRng(lo, hi)),
get: () => n
};
};
const rng = intRng(10, 20);
console.log(rng.get());
console.log(rng.get());
rng.next();
console.log(rng.get());
console.log(rng.get());
But having to do this shouldn't really be necessary for your application. Currently, the application uses non-idempotent functions that rely on global state, repeated/duplicate logic and deeply nested conditionals, so it's become too encumbered to easily work with.
I'd start by storing state in an object. A game like this can be modeled well by a finite state machine.
The below code is a naive implementation of this with plenty of room for improvement, but hopefully demonstrates the idea. It works for any number of players and it's fairly easy to add features to.
However, string messages are baked into business logic so the class is overburdened. A good next step would be creating a separate view class to abstract business logic from display. However, although the message strings are baked into the game logic, the DOM is decoupled. This makes it fairly easy for the caller to use the class in other UIs such as substituting the DOM for alert/prompt.
The below solution is far from the only way to approach this design problem.
class GuessingGame {
constructor(players=1, guesses=5, lo=0, hi=10) {
this.players = Array(players).fill().map(() => ({
guesses: guesses, score: 0
}));
this.guesses = guesses;
this.lowerBound = lo;
this.upperBound = hi;
this.state = this.initialize;
}
initialize() {
const {lowerBound: lo, upperBound: hi} = this;
this.players = this.players.map(({score}) => ({
guesses: this.guesses,
score: score
}));
this.target = ~~(Math.random() * (hi - lo) + lo);
this.currentPlayer = ~~(Math.random() * this.players.length);
this.state = this.guess;
this.message = `guess a number between ${lo} and ${hi - 1} ` +
`(inclusive), player ${this.currentPlayer}:`;
}
handleCorrectGuess() {
this.state = this.initialize;
this.players[this.currentPlayer].score++;
this.message = `player ${this.currentPlayer} guessed ` +
`${this.target} correctly! press 'enter' to continue.`;
}
handleNoGuessesLeft(guess) {
this.state = this.initialize;
this.players[this.currentPlayer].score--;
this.flash = `${guess} was not the number, player ` +
`${this.currentPlayer}.`;
this.message = `player ${this.currentPlayer} ran out of ` +
`guesses. the secret number was ${this.target}. press ` +
`'enter' to continue.`;
}
handleIncorrectGuess(guess) {
this.flash = `${guess} was not the number, player ` +
`${this.currentPlayer}.`;
this.currentPlayer = (this.currentPlayer + 1) % this.players.length;
const {lowerBound: lo, upperBound: hi} = this;
this.message = `guess a number between ${lo} and ${hi - 1} ` +
`(inclusive), player ${this.currentPlayer}:`;
}
guess(guess) {
if (String(+guess) !== String(guess)) {
this.flash = `sorry, ${guess || "that"} ` +
`isn't a valid number. try something else.`;
return;
}
if (this.target === +guess) {
this.handleCorrectGuess();
}
else if (!--this.players[this.currentPlayer].guesses) {
this.handleNoGuessesLeft(+guess);
}
else {
this.handleIncorrectGuess(+guess);
}
}
nextState(...args) {
this.flash = "";
return this.state(...args);
}
scoreBoard() {
return game.players.map((e, i) =>
`player ${i}: {score: ${e.score}, guesses remaining: ` +
`${e.guesses}} ${game.currentPlayer === i ? "<--" : ""}`
).join("\n");
}
}
const msgElem = document.getElementById("message");
const responseElem = document.getElementById("response");
const scoresElem = document.getElementById("scoreboard");
const game = new GuessingGame(3);
game.nextState();
msgElem.innerText = game.message;
scoresElem.innerText = game.scoreBoard();
let timeout;
responseElem.addEventListener("keydown", e => {
if (timeout || e.code !== "Enter") {
return;
}
game.nextState(e.target.value);
e.target.value = "";
e.target.disabled = true;
msgElem.innerText = game.flash;
clearTimeout(timeout);
timeout = setTimeout(() => {
msgElem.innerText = game.message;
scoresElem.innerText = game.scoreBoard();
timeout = null;
e.target.disabled = false;
e.target.focus();
}, game.flash ? 1300 : 0);
});
* {
background: white;
font-family: monospace;
font-size: 1.03em;
}
input {
margin-bottom: 1em;
margin-top: 1em;
}
<div id="message"></div>
<input id="response">
<div id="scoreboard"></div>
Well, your code is not organised, have lot of duplicates, you could devide it into functions anyway, you can add a boolean variable to check against when you should change the number, I don't know about your HTML code or css but I just added those elements according to you selectors, you can change the multiplayer function too.
var tries = 5;
var howMany = 0;
var wrong = 0;
var player1 = 0;
var player2 = 0;
var triesmulti = 10;
var turn = 'player 1';
var number;
var isAlreadyPlaying = false;
function start() {
var min = document.getElementById('min').value;
var max = document.getElementById('max').value;
if(!isAlreadyPlaying) {
isAlreadyPlaying = true;
number = Math.floor(Math.random() * (+max - +min)) + +min;
}
if (tries < 1) {
alert('You \don\'t have any more tries left. The number was \n' + number);
tries = 5;
wrong += 1;
document.getElementById('wrong').innerHTML = 'You have got the number wrong ' + wrong + ' times';
isAlreadyPlaying = false;
} else {
var guess = prompt();
if (guess == number) {
alert('You got the number right!\n' + number);
howMany += 1;
tries = 5;
document.getElementById('howMany').innerHTML = 'You have guessed the number ' + howMany + ' times';
document.getElementById('tries').innerHTML = 'You have 5 tries left';
isAlreadyPlaying = false;
} else {
alert('You got the number wrong.');
tries -= 1;
document.getElementById('tries').innerHTML = 'You have ' + tries + ' tries left';
setTimeout(start, 1000);
}
}
}
Min <input type="number" id="min" value="1"><br>
Max<input type="number" id="max" value="10"><br>
<button onclick="start()">Play</button>
<p id="wrong"></p>
<p id="howMany"></p>
<p id="tries"></p>

Defining read-only properties within a factory-function, using getters

So, it appears, there are two working approaches to defining read-only properties in JS - with Object.defineProperty or a getter.
In my case, I have to stick to the getter appoach, but considering the context, it does not seem to fit in just right, as I am implementing the getter within the LoyaltyCard factory-function.
Properties 'id', 'balance' and 'discount' have to be difined to read-only. Starting from 'id', there is a syntax error, returning: "Unexpected token, expected ;".
Is there a way to implement the getter neatly, as it is suggested in: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get or maybe I'm missing something?
function rand(min, max) {
return Math.ceil((max - min + 1) * Math.random()) + min - 1;
}
function generateId() {
return Array(4).fill(1).map(value => rand(1000, 9999)).join('-');
}
let LoyaltyCard = function(name, sum) {
this.owner = name;
this.id; //unfinished
this.balance = sum;
this.discount = 0;
this.orders = Array.of(sum);
}
LoyaltyCard.prototype.getFinalSum = function(sum) {
let calculatedDiscount;
if (this.balance >= 3000 && this.balance < 5000) {
calculatedDiscount = 3;
} else if (this.balance >= 5000 && this.balance < 10000){
calculatedDiscount = 5;
} else if (this.balance >= 10000) {
calculatedDiscount = 7;
}
Object.defineProperty(this, 'discount', {
value: calculatedDiscount
});
finalSum = sum * (1 - this.discount / 100);
this.orders.push(sum);
return finalSum;
}
LoyaltyCard.prototype.append = function(sum) {
this.orders.push(sum);
return Object.defineProperty(this, 'balance', {
value: this.balance += sum
});
}
LoyaltyCard.prototype.show = function() {
console.log(`Card ${this.id}:\nOwner: ${this.owner}\nBalance: ${this.balance} Q\nCurrent discount: ${this.discount} %\nOrders:\n #1 on ${this.orders[0]} Q\n #2 on ${this.orders[1]} Q`);
}
//Call sample:
const card = new LoyaltyCard('John Doe', 6300);
let newOrderSum = 7000;
let finalSum = card.getFinalSum(newOrderSum);
console.log(`The final order amount for the order on ${newOrderSum} Q using the loyalty card will be
${finalSum} Q. ${card.discount} %. discount applied`);
card.append(newOrderSum);
console.log(`Loyalty card balance after the purchase ${card.balance} Q.`);
card.show();
Please consider "module pattern" to encapsulate the data your want to store as "private". Here the demo
function LoyaltyCard(name, sum) {
let owner = name;
let id; // get id from your generateId()
let balance = sum;
let discount = 0;
let orders = Array.of(sum);
function getFinalSum(sum) {
/* implementation */
balance += sum; // DEMO ONLY
return sum; // DEMO ONLY
}
function append(sum) { /* implementation */ }
function show() { /* implementation */ }
return {
get name() {
return owner;
},
set name(name) {
owner = name;
},
get balance() {
return balance;
},
getFinalSum,
append,
};
}
const card = LoyaltyCard('peter', 100);
console.log(card.balance); // 100
console.log(card.getFinalSum(200)); // 200
console.log(card.balance); // 300
card.balance = 50; // silently fail
console.log(card.balance); // 300
For more information about "module pattern" please read YDKJS
https://github.com/getify/You-Dont-Know-JS/blob/master/scope%20%26%20closures/ch5.md

How to instantiate a class multiple times based on a user input?

I'm trying to create a board game and would like to instantiate the class Human based on a number of times provided by the user. Obviously I'm trying to assign a different ID per object and the following loop doesn't work in order to instantiate the number of players:
var question = prompt('how many players');
var numOfPlayers = parseInt(question);
class Human {
constructor (id) {
this.id = id;
this.health = 100;
this.hammer = false
this.knife = false;
this.sword = false;
this.baseballbat = false;
this.damage = 0;
this.location = {
x: Math.floor(Math.random() * 8),
y: Math.floor(Math.random() * 8)
}
}
moveTo(x, y){
this.location.x += x;
this.location.y += y;
}
}
var i;
for (i = 0; i < numOfPlayers; i++) {
const player = new Human(id = i);
}
Firstly, I hope I have understood what you are trying to achieve here. The scope of the "const player" is limited within the loop. If you want to be able to access it outside the loop you need to declare a list/array likewise.
Code may go like this for the same:
var players = [];
for(let i = 0; i < numOfPlayers; i++) {
players.push(new Human(i));
}
Note: If you don't want to use variable 'i' outside the loop you can declare it inside 'for' using 'let' keyword as can be seen in the code above.
class Human {
constructor (id){
this.id = id;
this.health = 100;
this.hammer = false
this.knife = false;
this.sword = false;
this.baseballbat = false;
this.damage = 0;
this.location = {
x:Math.floor(Math.random()*8),
y:Math.floor(Math.random()*8)
}
console.log(`Human created with id of ${id}`); //Remove this just to show you that your class is being instantiated for each 'player'
}
moveTo(x,y){
this.location.x += x;
this.location.y += y;
}
}
let numOfPlayers = prompt('How many players?');
const _init = () => {
if(parseInt(numOfPlayers) > 0) {
for (let i = 0; i < numOfPlayers; i++) {
new Human(i)
}
}
}
_init();

Categories

Resources