Challenge: Solution Review

This lesson will explain the solution to the problem from the last coding challenge.

We'll cover the following

Solution #

Press + to interact
const Ninja = function(name) {
this.points = 100
this.name = name
}
Ninja.prototype.punch = function(otherNinja) {
if(otherNinja.points > 0 && this.points > 0){
otherNinja.points -= 20
return `${otherNinja.name}'s points are ${otherNinja.points}`
}else{
return `Can't punch ${otherNinja.name}`
}
}
Ninja.prototype.kick = function(otherNinja) {
if(otherNinja.points > 0 && this.points > 0){
otherNinja.points -= 50
return `${otherNinja.name}'s points are ${otherNinja.points}`
}else{
return `Can't kick ${otherNinja.name}`
}
}
const ninja1 = new Ninja("Ninja1")
const ninja2 = new Ninja("Ninja2")
console.log(ninja1.kick(ninja2))
console.log(ninja2.punch(ninja1))
console.log(ninja1.kick(ninja2))
console.log(ninja1.punch(ninja2))
console.log(ninja2.kick(ninja1))

Explanation

In this challenge, you had to create a small game for ninja fighting using the prototype pattern. Two ninjas, ninja1 and ninja2, fight each other. They either have the option to kick or punch each other. Let’s look at how we achieved this using the prototype pattern.

First, we define the Ninja constructor function. It accepts the name parameter and sets the name property of the current object equal to it. Next, it defines the points property for the instantiated object and sets it equal to 100.

this.points = 100
this.name = name 

The next step is to define the punch and kick functions. One way to do that is as follows:

Press + to interact
const Ninja = function(name) {
this.points = 100
this.name = name
this.punch = function(otherNinja) {
if(otherNinja.points > 0 && this.points > 0){
otherNinja.points -= 20
return `${otherNinja.name}'s points are ${otherNinja.points}`
}else{
return `Can't punch ${otherNinja.name}`
}
}
this.kick = function(otherNinja) {
if(otherNinja.points > 0 && this.points > 0){
otherNinja.points -= 50
return `${otherNinja.name}'s points are ${otherNinja.points}`
}else{
return `Can't kick ${otherNinja.name}`
}
}
}
const ninja1 = new Ninja("Ninja1")
const ninja2 = new Ninja("Ninja2")
console.log(ninja1.kick(ninja2))
console.log(ninja2.punch(ninja1))
console.log(ninja1.kick(ninja2))
console.log(ninja1.punch(ninja2))
console.log(ninja2.kick(ninja1))

As you can see, this approach works completely fine and produces the correct output. So what’s wrong with it?

In the above solution, both the instances ninja1 and ninja2 get their copies of the punch and kick function upon instantiation. Imagine making a 1000 instances that would mean a 1000 copies of both these function for all 1000 instances! This will clutter the memory very easily.

Press + to interact
const Ninja = function(name) {
this.points = 100
this.name = name
this.punch = function(otherNinja) {
if(otherNinja.points > 0 && this.points > 0){
otherNinja.points -= 20
return `${otherNinja.name}'s points are ${otherNinja.points}`
}else{
return `Can't punch ${otherNinja.name}`
}
}
this.kick = function(otherNinja) {
if(otherNinja.points > 0 && this.points > 0){
otherNinja.points -= 50
return `${otherNinja.name}'s points are ${otherNinja.points}`
}else{
return `Can't kick ${otherNinja.name}`
}
}
}
const ninja1 = new Ninja("Ninja1")
const ninja2 = new Ninja("Ninja2")
console.log(ninja1.kick === ninja2.kick)
console.log(ninja1.punch === ninja2.punch)

As you can see from the output of the code above, both ninja1 and ninja2 have different copies for both the punch and kick functions.

As explained in a previous lesson, the prototype pattern is supposed to boost the performance and make the code more efficient. Hence, you might have noticed that in our solution, both the functions are defined on the prototype of Ninja.

Ninja.prototype.punch = function(otherNinja) {
     //some code... 
}

Ninja.prototype.kick = function(otherNinja) {
     //some code... 
}

So why have we done this? Since we have defined the functions on the prototype, both ninja1 and ninja2 instances refer to the same punch and kick functions; instead of having their copies of each. This makes the code more memory efficient.

Press + to interact
const Ninja = function(name) {
this.points = 100
this.name = name
}
Ninja.prototype.punch = function(otherNinja) {
if(otherNinja.points > 0 && this.points > 0){
otherNinja.points -= 20
return `${otherNinja.name}'s points are ${otherNinja.points}`
}else{
return `Can't punch ${otherNinja.name}`
}
}
Ninja.prototype.kick = function(otherNinja) {
if(otherNinja.points > 0 && this.points > 0){
otherNinja.points -= 50
return `${otherNinja.name}'s points are ${otherNinja.points}`
}else{
return `Can't kick ${otherNinja.name}`
}
}
const ninja1 = new Ninja("Ninja1")
const ninja2 = new Ninja("Ninja2")
console.log(ninja1.kick === ninja2.kick)
console.log(ninja1.punch === ninja2.punch)

From the output of the code above, you can see that both the instances have the reference to the same kick and punch functions.

The implementation of both these functions is simple. They accept otherNinja object as a parameter.

Ninja.prototype.punch = function(otherNinja)

Ninja.prototype.kick = function(otherNinja)

Next, they check if the points of both the ninjas are greater than 0.

if(otherNinja.points > 0 && this.points > 0)

If either of these conditions is false a message stating that the ninja can’t proceed with the attack is returned.

//punch function
//.....some code
return `Can't punch ${otherNinja.name}` 

//kick function
//some code...
return `Can't kick ${otherNinja.name}`

If both these conditions are satisfied, then the ninja is allowed to attack.

  • otherotherNinja's points are reduced by 20 if it is a punch.

    otherNinja.points -= 20
    
  • otherotherNinja's points are reduced by 50 if it is a kick.

    otherNinja.points -= 50
    

Let’s discuss the abstract pattern in the next lesson.