Vue.js Pokemon Battle Tutorial

Michael Mangialardi
Feb 8, 2017 · 36 min read

What you’re getting into: A very detailed read that goes through the creation of a Pokemon Battle using Vue.js. I try my best not to make assumptions, but there’s a lot of stuff to go through (it took me 15 hours just to write this post). If you want to learn more about the basics of Vue.js to make fun apps, you can check out my video course called Power Up With Pure CSS Images & Vue.js to Make Fun Apps. In this course, the basics of Vue.js will be discussed in much greater detail.


See full code: http://codepen.io/mikemang/pen/zNJZYg/

See live demo: http://codepen.io/mikemang/live/zNJZYg

Introduction

After graduating from school, I decided that I wanted to focus in on web development. I quickly realized that I needed to be even more specific as to what skill within web development I wanted to focus on.

After much practice, I have finally found much enjoyment of doing frontend development with a specific emphasis on design. Instead of making dynamic web applications, I prefer to mini applications like Mad Libs, Connect 4, etc.

Even if that’s not your thing, I think making fun, mini web apps is a great way to practice learning a new framework. After trying React and Vue.js, I have fallen madly in love with the simplicity and readability Vue offers, especially for someone in my niche.

For both React and Vue.js, however, there’s not a ton of tutorials out there for making fun, mini web apps like I enjoy making. Therefore, I thought it would be cool to write a super detailed post on making a Pokemon Battle with Vue.js.

If you haven’t read my previous blogs, I don’t like to make assumptions and I really like to break things down. Whether this is your first introduction to Vue.js or you are just looking to do something different with it, I hope you enjoy this post!

Breaking It Down

Alright before we get started, let’s break down what we will be doing.

We are going to start off with a template that is the container for our Pokemon Battle app. Go ahead and fork it.

I won’t be going into detail on how I coded this. If you aren’t familiar with pure CSS images and are curious on how I did this, I recommend you read my Beginner’s Guide to Pure CSS Images.

With this template, we need to add the following:

  1. Images for both Pokemon
  2. Name, level, and HP for each Pokemon
  3. In-battle options which will go in the white box in the bottom-right corner
  4. In-battle text updates which will go in the box to the left of the in-battle options

In terms of functionality, we need to do the following:

  1. Allow user to be able to attack with their Pokemon that will do damage to the opposing Pokemon
  2. Have opponent attack after user’s Pokemon turn that will do damage to user’s Pokemon
  3. Continue the battle if no Pokemon has fainted or end the battle if a Pokemon has fainted
  4. Update in-battle text throughout the process
  5. Allow a new battle to initiated when one ends

Filling in the Template

First things first, let’s explain what we have.

In our HTML, it is just a pure CSS image as previously mentioned and a title. All of this is wrapped around the following:

<div id="app">

This wrapper is associated with a Vue.js instance. Let’s look at that.

var app = new Vue({
el: '#app',
data: {

This is the basic shell of a Vue.js instance. The el associates this instance with the <div id=”app”></div> that we had in our HTML. Because of this, we can have some data within data: { } that we can use in our HTML.

We can manipulate that data, so it can be dynamic, by functions within methods: {}. These methods will be called by various event handlers.

So to start, let’s review what we needed to add to our template and add it in the data our Vue.js instance called app.

In the template, we need to add the following:

  1. Images for both Pokemon
  2. Name, level, and HP for each Pokemon
  3. In-battle options which will go in the white box in the bottom-right corner
  4. In-battle text updates which will go in the box to the left of the in-battle options

We want something similar to this:

Add Data to Instance

So…we can have some data in our Vue.js instance like so:

var app = new Vue({
el: '#app',
data: {
userPokemonSrc: "http://guidesmedia.ign.com/guides/059687/images/blackwhite/pokemans_006.gif",
opponentPokemonSrc: "http://pre01.deviantart.net/959a/th/pre/f/2016/075/4/6/095_onix_by_rayo123000-d9vbjj3.png",
userPokemon: "Charizard",
opponentPokemon: "Onyx",
userHP: 100,
opponentHP: 100,
userLevel: 50,
opponentLevel: 50,
battleText: "What will Charizard do?",
battleOptions: ["Fight", "Pokemon", "Item", "Run"]
},

methods:{

}

})

Here we specified the image src for both Pokemon:

data: {
userPokemonSrc: "http://guidesmedia.ign.com/guides/059687/images/blackwhite/pokemans_006.gif",
opponentPokemonSrc: "
http://pre01.deviantart.net/959a/th/pre/f/2016/075/4/6/095_onix_by_rayo123000-d9vbjj3.png",

We also have the names for both Pokemon:

data: {
userPokemonSrc: "http://guidesmedia.ign.com/guides/059687/images/blackwhite/pokemans_006.gif",
opponentPokemonSrc: "http://pre01.deviantart.net/959a/th/pre/f/2016/075/4/6/095_onix_by_rayo123000-d9vbjj3.png",
userPokemon: "Charizard",
opponentPokemon: "Onyx",

Then, we also can add the HP and levels like so:

data: {
userPokemonSrc: "http://guidesmedia.ign.com/guides/059687/images/blackwhite/pokemans_006.gif",
opponentPokemonSrc: "http://pre01.deviantart.net/959a/th/pre/f/2016/075/4/6/095_onix_by_rayo123000-d9vbjj3.png",
userPokemon: "Charizard",
opponentPokemon: "Onyx",
userHP: 100,
opponentHP: 100,
userLevel: 50,
opponentLevel: 50,

Lastly, we added a string with our default battle text and an array with our battle options:

var app = new Vue({
el: '#app',
data: {
userPokemonSrc: "http://guidesmedia.ign.com/guides/059687/images/blackwhite/pokemans_006.gif",
opponentPokemonSrc: "http://pre01.deviantart.net/959a/th/pre/f/2016/075/4/6/095_onix_by_rayo123000-d9vbjj3.png",
userPokemon: "Charizard",
opponentPokemon: "Onyx",
userHP: 100,
opponentHP: 100,
userLevel: 50,
opponentLevel: 50,
battleText: "What will Charizard do?",
battleOptions: ["Fight", "Pokemon", "Item", "Run"]

},

methods:{

}

})

Alright, cool!

Now, we need to inject this data into our HTML so we can see it, so we do (changes in bold):

<body>

By doing this we should get:

Pretty cool, right?

Alright, let’s discuss how this worked.

Injecting Data

When we needed to inject data between some HTML tags, we just used {{[name of data]}} like so:

<h2 class="pokemon">{{opponentPokemon}}</h2>
<h4 class="level">Lvl. {{opponentLevel}}</h4>
<h2 class="pokemon">{{userPokemon}}</h2>
<h4 class="level">Lvl. {{userLevel}}</h4>
<h4 class="hp">{{userHP}}/{{userHP}}</h4>
<div class="battle-text text-box-left">{{battleText}}</div>

Whatever is in between the double curly will just output whatever is stored in the data of our Vue.js instance called app. As you can see, we can also add some text around the injected data.

Now, our images were controlled by the data in our Vue.js instance in a different fashion. In our data, we had image links that were to be used as the src in two different image tags. Because we were binding something within a tag, we use v-bind:[what we want to bind] = “[name of some data]” which for us was:

<img v-bind:src="opponentPokemonSrc" class="pokemon-top" />
<img v-bind:src="userPokemonSrc" class="pokemon-bottom" />

Style Binding

With that in mind, we can also bind the style of an HTML tag. We will need to use this to fill our HP bars which are pure CSS.

So let’s add to our HTML for the HP bar of the user Pokemon and opponent Pokemon (lines highlighted in bold):

<body>

Let’s look what we added:

<div v-bind:style="opponentHpBar" class="hp-bar-fill"></div>
<div v-bind:style="userHpBar" class="hp-bar-fill"></div>

As you can see the line we added the v-bind:style to has a class called .hp-bar-fill. This class has a color of orange (#FF800) and has no width assigned. Because there is no width, we don’t see an orange bar.

If we add a width of let’s say, 60%, we can see the orange bar:

.hp-bar-fill{
position: absolute;
height: 100%;
width: 60%;
border-radius: 20px;
background: #FF8800;
}

So let’s think about how are HP bars should work.

Whenever a Pokemon takes damage, the HP bar should be updated. Therefore, we need to control the width of the HP bars dynamically. We also need to account for the fact that the HP bars will be different for each Pokemon.

This is why we added v-bind:style=”opponentHpBar” and v-bind:style=”userHpBar”. With this, we can control the width with data in our Vue.js instance. We can control it for specifically the opponent Pokemon and user Pokemon accordingly. It’s also important to note that Vue.js will only control the styling of what we specify.

Now, we don’t have any data called opponentHpBar or userHPBar in our Vue instance.

Let’s edit our Vue instance’s data so we can see this come together.

var app = new Vue({
el: '#app',
data: {
userPokemonSrc: "http://guidesmedia.ign.com/guides/059687/images/blackwhite/pokemans_006.gif",
opponentPokemonSrc: "http://pre01.deviantart.net/959a/th/pre/f/2016/075/4/6/095_onix_by_rayo123000-d9vbjj3.png",
userPokemon: "Charizard",
opponentPokemon: "Onyx",
userHP: 100,
opponentHP: 100,
userLevel: 50,
opponentLevel: 50,
battleText: "What will Charizard do?",
battleOptions: ["Fight", "Pokemon", "Item", "Run"],
userHpBar: {
width: "100%"
},
opponentHpBar: {
width: "100%"
}

},
methods:{

}

})

Awesome! Now the HP bars are controlled via Vue.js using the following syntax:

//within data: {...}
[data name referenced in v-bind:style]: {
[CSS value to control]: "[value]"
}

Alrighty! Our template is filled and we’ve learned some cool things about Vue.js, now let’s manipulate the data to get this functioning.

Manipulating Our Data

First, let’s review the functionalities we need.

In terms of functionality, we need to do the following:

  1. Allow user to be able to attack with their Pokemon that will do damage to the opposing Pokemon
  2. Have opponent attack after user’s Pokemon turn that will do damage to user’s Pokemon
  3. Continue the battle if no Pokemon has fainted or end the battle if a Pokemon has fainted
  4. Update in-battle text in-battle options throughout the process
  5. Allow a new battle to initiated when one ends

I’m not going to go in order from this list. Instead, I’ll do from easiest to most challenging. Let’s start with #4.

Our in-battle text is going to be updated depending on what battle options are selected so let’s do our battle options first.

While you are more than welcome to make this more epic once we finish the tutorial, I am not going to add functionality for anything except fighting (this blog post is already a 6 hour process).

So I let’s start by updating our battle text when “Pokemon”, “Item” or “Run” are clicked.

We will do this by calling a function (processOption) in our methods whenever an option is clicked. Let’s add the shell of our function:

methods:{
processOption: function(){

}
}

Now, we need to call this via an event handler…

We will use event handlers like so:

<h4 v-on:click="processOption" class="battle-text-top-left">{{battleOptions[0]}}</h4>
<h4 v-on:click="processOption" class="battle-text-top-right">{{battleOptions[1]}}</h4>
<h4 v-on:click="processOption" class="battle-text-bottom-left">{{battleOptions[2]}}</h4>
<h4 v-on:click="processOption" class="battle-text-bottom-right">{{battleOptions[3]}}</h4>

The event handler syntax is:

v-on:[event]="[function name found in methods]"

So when any of our options are clicked, we will trigger processOption which currently does nothing.

In our processOption, we will need to manipulate data depending on which option is clicked. Therefore, let’s add a parameter to our function like so:

methods:{
processOption: function(option){

}
}

Now, let’s pass a parameter so our function nows which option was clicked and we can update our battle text accordingly.

<h4 v-on:click="processOption(1)" class="battle-text-top-left">{{battleOptions[0]}}</h4>
<h4 v-on:click="processOption(2)" class="battle-text-top-right">{{battleOptions[1]}}</h4>
<h4 v-on:click="processOption(3)" class="battle-text-bottom-left">{{battleOptions[2]}}</h4>
<h4 v-on:click="processOption(4)" class="battle-text-bottom-right">{{battleOptions[3]}}</h4>

Next, we update our function to handle these options:

methods:{
processOption: function(option){
switch(option){
case 1:
//handle fight, will be done later
break;
case 2:
//handle pokemon
break;
case 3:
//handle item
break;
case 4:
//handle run
break;
}
}
}

Now, we can add our handles to cases 2–4 to update the in-battle text:

methods:{
processOption: function(option){
switch(option){
case 1:
//handle fight
break;
case 2:
//handle pokemon
this.battleText = "You're our only hope " + this.userPokemon + "!"
break;
case 3:
//handle item
this.battleText = "No items in bag."
break;
case 4:
//handle run
this.battleText = "Can't escape."
break;
}
}
}

We can now see the battle text update when we click “Pokemon”, “Item”, or “Run”.

We did this by accessing our data using this. If we wanted to access data from another Vue instance, we would use the name of the Vue instance (i.e app.battleText instead of this.battleText).

As you can tell, data manipulation is very easy with Vue.js.

Before we move on, we need to make a slight modification. We want these specific battle texts to show for a brief period then revert back to our default text. Therefore, let’s add setTimeout. (Note: from now on, you will need to wait for the battle text to be updated before clicking another option)

methods:{
processOption: function(option){
switch(option){
case 1:
//handle fight
break;
case 2:
//handle pokemon
setTimeout(() => {
this.battleText = "What will " + this.userPokemon + " do?"
}, 2000);


this.battleText = "You're our only hope " + this.userPokemon + "!"

break;
case 3:
//handle item
setTimeout(() => {
this.battleText = "What will " + this.userPokemon + " do?"
}, 2000);

this.battleText = "No items in bag."
break;
case 4:
//handle run
setTimeout(() => {
this.battleText = "What will " + this.userPokemon + " do?"
}, 2000);

this.battleText = "Can't escape."
break;
}
}

By using setTimeout, the battleText value is updated for 2 seconds (2000 ms) before returning to the default text of “What will [Charizard] do?”.

Test it out. When you’re ready, let’s handle the fight option which will be more complex.

So let’s think about this. When we click the fight button, we want to populate the in-battle options with all the possible attacks that can be selected. The in-battle options are currently stored in an array. There are a couple of options.

We could either A) Repopulate the array with different options or B) Have a fightOptions array that is initially inactive, when the fight option is selected the battleOptions will be inactive and fightOptions becomes active

Vue.js is awesome for conditionally rendering to allow us to toggle on and off elements of our app with ease, therefore, we will go with option B.

First, let’s add an array called fightOptions within data: {}:

data: {
//...

Next, let’s add our attacks (fightOptions values) into out HTML.

<div class="text-box-right">
//battleOptions

Next, let’s wrap the tags for both battleOptions and fightOptions with a simple parent div:

<div class="text-box-right">
//battleOptions

Currently, both options will be overlapping.

We need to have fightOptions be inactive while battleOptions is active until “Fight” is clicked.

So we add some Vue.js conditional rendering magic using v-if:

<div v-if="optionsOn" id="battleOptions">
<h4 v-on:click="processOption(1)" class="battle-text-top-left">{{battleOptions[0]}}</h4>
<h4 v-on:click="processOption(2)" class="battle-text-top-right">{{battleOptions[1]}}</h4>
<h4 v-on:click="processOption(3)" class="battle-text-bottom-left">{{battleOptions[2]}}</h4>
<h4 v-on:click="processOption(4)" class="battle-text-bottom-right">{{battleOptions[3]}}</h4>
</div>

optionsOn and fightOn refer to the names of some data in our Vue instance that will be set to true or false. If they have a value of true, they will be active and be rendered. If they have a value of false, they will not be rendered.

Therefore, we will add the following to our Vue.js instance:

data: {
//....

By default, our battleOptions will render since optionsOn is set to true and fightOptions won’t render since fightOn is set to false.

We should now see this reflected in our app.

With that settled, let’s add the handle in our processOption function for when fight is clicked (case 1).

methods:{
processOption: function(option){
switch(option){
case 1:
//handle fight
this.optionsOn = false
this.fightOn = true

break;
case 2:
//handle pokemon
setTimeout(() => {
this.battleText = "What will " + this.userPokemon + " do?"
}, 2000);

this.battleText = "You're our only hope " + this.userPokemon + "!"

break;
case 3:
//handle item
setTimeout(() => {
this.battleText = "What will " + this.userPokemon + " do?"
}, 2000);
this.battleText = "No items in bag."
break;
case 4:
//handle run
setTimeout(() => {
this.battleText = "What will " + this.userPokemon + " do?"
}, 2000);
this.battleText = "Can't escape."
break;
}
}
}

Here, we simply set optionsOn equal to false and fightOptions equal to true.

We can now click fight and see our attacks.

The next part of our app will be handling our attacks. We need to do this in a separate function so let’s add the skeleton in our methods: {…}

methods: {
//...
processAttack: function(attack){

}

This function will work very similarly to our processOption.

Next, we need to update the event handlers in out HTML.

<div v-if="fightOn" id="fightOptions">
<h4 v-on:click="processAttack(1)" class="battle-text-top-left">{{fightOptions[0]}}</h4>
<h4 v-on:click="processAttack(2)" class="battle-text-top-right">{{fightOptions[1]}}</h4>
<h4 v-on:click="processAttack(3)" class="battle-text-bottom-left">{{fightOptions[2]}}</h4>
<h4 v-on:click="processAttack(4)" class="battle-text-bottom-right">{{fightOptions[3]}}</h4>
</div>

Then, we can add a switch statement in processAttack:

processAttack: function(attack){
switch(attack){
case 1:
//handle scratch
break;
case 2:
//handle fly

break;
case 3:
//handle flamethrower
break;
case 4:
//handle ember
break;
}

}

We are ready to handle our attacks. This is where things will get more complex. So before we get overwhelmed, let’s break down what we have to do and knock it out piece by piece:

  1. Add to our data to have value for damage of each attack (how much HP will be taken away from other Pokemon)
  2. Subtract HP from other Pokemon by attack’s damage
  3. Update the width of HP bar to match HP remaining
  4. Have battle text update to say which attack was selected
  5. Check if HP is 0. If not, opponent Pokemon attacks and we do the same steps 1–4 but for attacks doing damage to the user Pokemon not opponent Pokemon (We will worry about this step later)

Let’s go in order and knock steps 1–4 out.

In our data: {…}, let’s add an array called userAttackDamage which will contain the damage values:

userAttackDamage: [15,40,50,25]

Now, we will subtract from our opponentHP for each attack:

processAttack: function(attack){
switch(attack){
case 1:
//handle scratch
this.opponentHP -= this.userAttackDamage[attack-1]
break;
case 2:
//handle fly
this.opponentHP -= this.userAttackDamage[attack-1]
break;
case 3:
//handle flamethrower
this.opponentHP -= this.userAttackDamage[attack-1]
break;
case 4:
//handle ember
this.opponentHP -= this.userAttackDamage[attack-1]
break;
}
}

By using an array, we simplified things. We just do the attack-1 as our index since the attack parameter is 1–4 and the array index is 0–3.

Now, let’s add the code to adjust the HP bar fill:

data: {
//...

Because our width is a string of “[value]%”, we added opponentFill so we can subtract by the attack’s damage and then concatenate it back to a string and set as new value of this.opponentHpBar.width.

We can now test this out.

This code needs some adjustment. For example, we don’t want a negative width. So, we need to add a condition that if the fill is less than 0, then we just set the width to 0%.

processAttack: function(attack){
switch(attack){
case 1:
//handle scratch
this.opponentHP -= this.userAttackDamage[attack-1]
//edit if HP !== 0
this.opponentFill -= this.userAttackDamage[attack-1]
if(this.opponentFill <= 0){
this.opponentHpBar.width = "0%"
} else{
this.opponentHpBar.width = this.opponentFill + "%"
}

break;
case 2:
//handle fly
this.opponentHP -= this.userAttackDamage[attack-1]
//edit if HP !== 0
this.opponentFill -= this.userAttackDamage[attack-1]
if(this.opponentFill <= 0){
this.opponentHpBar.width = "0%"
} else{
this.opponentHpBar.width = this.opponentFill + "%"
}

break;
case 3:
//handle flamethrower
this.opponentHP -= this.userAttackDamage[attack-1]
//edit if HP !== 0
this.opponentFill -= this.userAttackDamage[attack-1]
if(this.opponentFill <= 0){
this.opponentHpBar.width = "0%"
} else{
this.opponentHpBar.width = this.opponentFill + "%"
}

break;
case 4:
//handle ember
this.opponentHP -= this.userAttackDamage[attack-1]
//edit if HP !== 0
this.opponentFill -= this.userAttackDamage[attack-1]
if(this.opponentFill <= 0){
this.opponentHpBar.width = "0%"
} else{
this.opponentHpBar.width = this.opponentFill + "%"
}

break;
}
}
}

Next, we just need to update the in-battle text depending on the attack selected, similarly to our handling of “Pokemon”, “Fight”, and “Run” in battleOptions.

processAttack: function(attack){
switch(attack){
case 1:
//handle scratch
this.opponentHP -= this.userAttackDamage[attack-1]
//edit if HP !== 0
this.opponentFill -= (this.userAttackDamage[attack-1])
if(this.opponentFill <= 0){
this.opponentHpBar.width = "0%"
} else{
this.opponentHpBar.width = this.opponentFill + "%"
}
setTimeout(() => {
this.battleText = "What will " + this.userPokemon + " do?"
}, 2000);

this.battleText = this.userPokemon + " used " + this.fightOptions[attack-1] + "!"

break;
case 2:
//handle fly
this.opponentHP -= this.userAttackDamage[attack-1]
//edit if HP !== 0
this.opponentFill -= (this.userAttackDamage[attack-1])
if(this.opponentFill <= 0){
this.opponentHpBar.width = "0%"
} else{
this.opponentHpBar.width = this.opponentFill + "%"
}
setTimeout(() => {
this.battleText = "What will " + this.userPokemon + " do?"
}, 2000);

this.battleText = this.userPokemon + " used " + this.fightOptions[attack-1] + "!"

break;
case 3:
//handle flamethrower
this.opponentHP -= this.userAttackDamage[attack-1]
//edit if HP !== 0
this.opponentFill -= (this.userAttackDamage[attack-1])
if(this.opponentFill <= 0){
this.opponentHpBar.width = "0%"
} else{
this.opponentHpBar.width = this.opponentFill + "%"
}
setTimeout(() => {
this.battleText = "What will " + this.userPokemon + " do?"
}, 2000);

this.battleText = this.userPokemon + " used " + this.fightOptions[attack-1] + "!"

break;
case 4:
//handle ember
this.opponentHP -= this.userAttackDamage[attack-1]
//edit if HP !== 0
this.opponentFill -= (this.userAttackDamage[attack-1])
if(this.opponentFill <= 0){
this.opponentHpBar.width = "0%"
} else{
this.opponentHpBar.width = this.opponentFill + "%"
}
setTimeout(() => {
this.battleText = "What will " + this.userPokemon + " do?"
}, 2000);

this.battleText = this.userPokemon + " used " + this.fightOptions[attack-1] + "!"

break;
}
}

Cool beans! Steps 1–4 are done!

Let’s look at what’s remaining.

  1. Check if HP is 0. If not, opponent Pokemon attacks and we do the same steps 1–4 but for attacks doing damage to the user Pokemon not opponent Pokemon (We will worry about this step later)

First, we add another function under methods: {…} called checkOpponentHp:

checkOpponentHp: function(){

This function will check the opponent’s HP. If it is now 0, then we update the in-battle text to say “[opponent Pokemon] fainted” for a brief period and then ask the user to play again. If it is greater than 0, a function (which we don’t have written) will be called that will trigger the opponent Pokemon’ attack.

The checkOpponentHp will just return true or false:

checkOpponentHp: function(){
if(this.opponentHP <= 0){

return true

} else{
return false
}
}

Then we update processAction to add our calls and update the in-battle text depending on the result:

processAttack: function(attack){
switch(attack){
case 1:
//handle scratch
this.opponentHP -= this.userAttackDamage[attack-1]
//edit if HP !== 0
this.opponentFill -= (this.userAttackDamage[attack-1])
if(this.opponentFill <= 0){
this.opponentHpBar.width = "0%"
} else{
this.opponentHpBar.width = this.opponentFill + "%"
}
if(this.checkOpponentHp()){
this.battleText = this.opponentPokemon + " fainted! Play again?"
} else if(this.checkOpponentHp() === false) {
setTimeout(() => {
this.battleText = "What will " + this.userPokemon + " do?"
}, 2000);

this.battleText = this.userPokemon + " used " + this.fightOptions[attack-1] + "!"
//call opponent attack function

}
break;
case 2:
//handle fly
this.opponentHP -= this.userAttackDamage[attack-1]
//edit if HP !== 0
this.opponentFill -= (this.userAttackDamage[attack-1])
if(this.opponentFill <= 0){
this.opponentHpBar.width = "0%"
} else{
this.opponentHpBar.width = this.opponentFill + "%"
}
if(this.checkOpponentHp()){
this.battleText = this.opponentPokemon + " fainted! Play again?"
} else if(this.checkOpponentHp() === false) {
setTimeout(() => {
this.battleText = "What will " + this.userPokemon + " do?"
}, 2000);

this.battleText = this.userPokemon + " used " + this.fightOptions[attack-1] + "!"
//call opponent attack function

}
break;
case 3:
//handle flamethrower
this.opponentHP -= this.userAttackDamage[attack-1]
//edit if HP !== 0
this.opponentFill -= (this.userAttackDamage[attack-1])
if(this.opponentFill <= 0){
this.opponentHpBar.width = "0%"
} else{
this.opponentHpBar.width = this.opponentFill + "%"
}
if(this.checkOpponentHp()){
this.battleText = this.opponentPokemon + " fainted! Play again?"
} else if(this.checkOpponentHp() === false) {
setTimeout(() => {
this.battleText = "What will " + this.userPokemon + " do?"
}, 2000);

this.battleText = this.userPokemon + " used " + this.fightOptions[attack-1] + "!"
//call opponent attack function

}
break;
case 4:
//handle ember
this.opponentHP -= this.userAttackDamage[attack-1]
//edit if HP !== 0
this.opponentFill -= (this.userAttackDamage[attack-1])
if(this.opponentFill <= 0){
this.opponentHpBar.width = "0%"
} else{
this.opponentHpBar.width = this.opponentFill + "%"
}
if(this.checkOpponentHp()){
this.battleText = this.opponentPokemon + " fainted! Play again?"
} else if(this.checkOpponentHp() === false) {
setTimeout(() => {
this.battleText = "What will " + this.userPokemon + " do?"
}, 2000);

this.battleText = this.userPokemon + " used " + this.fightOptions[attack-1] + "!"
//call opponent attack function

}
break;
}
}

What we are doing here is calling this.checkOpponentHp after we update the opponent’s HP based on which attack was selected.

If this.checkOpponentHp() returns true, then the HP is less than or equal to 0 and the in-battle text displays that the opponent Pokemon has fainted. Next, the user should be asked to play again and we handle the input.

Else, this.checkOpponentHp() returns false, meaning the battle should continue. The in-battle text displays the attack selected. If this is the case, a function needs to be called to simulate an opponent attack.

Alright, everything is looking great so far! The next thing we will focus on is handling when a Pokemon faints.

First, we will add a new function called processFaint under methods: {…}.

processFaint: function(){

}

Now, let’s add the calls in processAction:

processAttack: function(attack){
switch(attack){
case 1:
//handle scratch
this.opponentHP -= this.userAttackDamage[attack-1]
//edit if HP !== 0
this.opponentFill -= (this.userAttackDamage[attack-1])
if(this.opponentFill <= 0){
this.opponentHpBar.width = "0%"
} else{
this.opponentHpBar.width = this.opponentFill + "%"
}
if(this.checkOpponentHp()){
this.battleText = this.opponentPokemon + " fainted! Play again?"
this.processFaint()
} else if(this.checkOpponentHp() === false) {
setTimeout(() => {
this.battleText = "What will " + this.userPokemon + " do?"
}, 2000);

this.battleText = this.userPokemon + " used " + this.fightOptions[attack-1] + "!"
//call opponent attack function
}
break;
case 2:
//handle fly
this.opponentHP -= this.userAttackDamage[attack-1]
//edit if HP !== 0
this.opponentFill -= (this.userAttackDamage[attack-1])
if(this.opponentFill <= 0){
this.opponentHpBar.width = "0%"
} else{
this.opponentHpBar.width = this.opponentFill + "%"
}
if(this.checkOpponentHp()){
this.battleText = this.opponentPokemon + " fainted! Play again?"
this.processFaint()
} else if(this.checkOpponentHp() === false) {
setTimeout(() => {
this.battleText = "What will " + this.userPokemon + " do?"
}, 2000);

this.battleText = this.userPokemon + " used " + this.fightOptions[attack-1] + "!"
//call opponent attack function
}
break;
case 3:
//handle flamethrower
this.opponentHP -= this.userAttackDamage[attack-1]
//edit if HP !== 0
this.opponentFill -= (this.userAttackDamage[attack-1])
if(this.opponentFill <= 0){
this.opponentHpBar.width = "0%"
} else{
this.opponentHpBar.width = this.opponentFill + "%"
}
if(this.checkOpponentHp()){
this.battleText = this.opponentPokemon + " fainted! Play again?"
this.processFaint()
} else if(this.checkOpponentHp() === false) {
setTimeout(() => {
this.battleText = "What will " + this.userPokemon + " do?"
}, 2000);

this.battleText = this.userPokemon + " used " + this.fightOptions[attack-1] + "!"
//call opponent attack function
}
break;
case 4:
//handle ember
this.opponentHP -= this.userAttackDamage[attack-1]
//edit if HP !== 0
this.opponentFill -= (this.userAttackDamage[attack-1])
if(this.opponentFill <= 0){
this.opponentHpBar.width = "0%"
} else{
this.opponentHpBar.width = this.opponentFill + "%"
}
if(this.checkOpponentHp()){
this.battleText = this.opponentPokemon + " fainted! Play again?"
this.processFaint()
} else if(this.checkOpponentHp() === false) {
setTimeout(() => {
this.battleText = "What will " + this.userPokemon + " do?"
}, 2000);

this.battleText = this.userPokemon + " used " + this.fightOptions[attack-1] + "!"
//call opponent attack function
}
break;
}
}

In our new processFaint, we need to update a few things:

  1. Update options to “Yes” and “No”
  2. Toggle off fainted Pokemon image

First, let’s add the following to our HTML:

<div v-if="optionsOn" id="battleOptions">...</div>
<div v-if="fightOn" id="fightOptions">...</div>
<div v-if="endOn" id="endOptions">
<h4 class="battle-text-top-left">{{endOptions[0]}}</h4>
<h4 class="battle-text-top-right">{{endOptions[1]}}</h4>
</div>

Like our other options, we want to have end of the battle options (Yes and No) to render when endOn is equal to true.

Next, let’s add this data in our Vue instance.

endOptions: ["Yes", "No"],
endOn: false,

Now, back to our processFaint function. We will add the code to turn endOn to true and fightOn to false.

processFaint: function(){
this.fightOn = false;
this.endOn = true;
}

Now, let’s add the code to toggle off the fainted Pokemon. First, we need to make both our Pokemon images controlled via conditional rendering. So, we will add the following in our HTML:

<div class="box-top-right">
<img v-if="opponentAlive" v-bind:src="opponentPokemonSrc" class="pokemon-top" />
</div>
<div class="box-bottom-left">
<img v-if="userAlive" v-bind:src="userPokemonSrc" class="pokemon-bottom" />
</div>

We then add this data to the Vue instance:

userAlive: true,
opponentAlive: true,

Now, we update processFaint to handle accordingly:

processFaint: function(pokemon){
this.fightOn = false;
this.endOn = true;
if(pokemon === 1){
this.opponentAlive = false;
} else {
this.userAlive = false;
}

}

We need to know which Pokemon fainted to know which image to toggle off. Here, we added a parameter called Pokemon that will specify that. When an opponent faints, 1 will be passed. When a user faints, 2 will be passed.

We don’t have any code for handling an opponent’s attack. We will just add a parameter of 1 to the call to processFaint for when an opponent faints.

processAttack: function(attack){
switch(attack){
case 1:
//handle scratch
this.opponentHP -= this.userAttackDamage[attack-1]
//edit if HP !== 0
this.opponentFill -= (this.userAttackDamage[attack-1])
if(this.opponentFill <= 0){
this.opponentHpBar.width = "0%"
} else{
this.opponentHpBar.width = this.opponentFill + "%"
}
if(this.checkOpponentHp()){
this.battleText = this.opponentPokemon + " fainted! Play again?"
this.processFaint(1)
} else if(this.checkOpponentHp() === false) {
setTimeout(() => {
this.battleText = "What will " + this.userPokemon + " do?"
}, 2000);

this.battleText = this.userPokemon + " used " + this.fightOptions[attack-1] + "!"
//call opponent attack function
}
break;
case 2:
//handle fly
this.opponentHP -= this.userAttackDamage[attack-1]
//edit if HP !== 0
this.opponentFill -= (this.userAttackDamage[attack-1])
if(this.opponentFill <= 0){
this.opponentHpBar.width = "0%"
} else{
this.opponentHpBar.width = this.opponentFill + "%"
}
if(this.checkOpponentHp()){
this.battleText = this.opponentPokemon + " fainted! Play again?"
this.processFaint(1)
} else if(this.checkOpponentHp() === false) {
setTimeout(() => {
this.battleText = "What will " + this.userPokemon + " do?"
}, 2000);

this.battleText = this.userPokemon + " used " + this.fightOptions[attack-1] + "!"
//call opponent attack function
}
break;
case 3:
//handle flamethrower
this.opponentHP -= this.userAttackDamage[attack-1]
//edit if HP !== 0
this.opponentFill -= (this.userAttackDamage[attack-1])
if(this.opponentFill <= 0){
this.opponentHpBar.width = "0%"
} else{
this.opponentHpBar.width = this.opponentFill + "%"
}
if(this.checkOpponentHp()){
this.battleText = this.opponentPokemon + " fainted! Play again?"
this.processFaint(1)
} else if(this.checkOpponentHp() === false) {
setTimeout(() => {
this.battleText = "What will " + this.userPokemon + " do?"
}, 2000);

this.battleText = this.userPokemon + " used " + this.fightOptions[attack-1] + "!"
//call opponent attack function
}
break;
case 4:
//handle ember
this.opponentHP -= this.userAttackDamage[attack-1]
//edit if HP !== 0
this.opponentFill -= (this.userAttackDamage[attack-1)
if(this.opponentFill <= 0){
this.opponentHpBar.width = "0%"
} else{
this.opponentHpBar.width = this.opponentFill + "%"
}
if(this.checkOpponentHp()){
this.battleText = this.opponentPokemon + " fainted! Play again?"
this.processFaint(1)
} else if(this.checkOpponentHp() === false) {
setTimeout(() => {
this.battleText = "What will " + this.userPokemon + " do?"
}, 2000);

this.battleText = this.userPokemon + " used " + this.fightOptions[attack-1] + "!"
//call opponent attack function
}
break;
}
}

If we test this out, we should see the opponent Pokemon disappear when it faints.


Ok…I know this is a lot so far. Feel free to take a break or return to this article another time. When you’re ready, we will finish up.


We have the following left to complete:

  1. Write function to handle opponent attacks
  2. Write function to handle user input about playing again

We will start with the former.

Let’s break down this step even more. We have to do the following:

  1. Add data for opponent attacks and their damage values
  2. Have attack randomly selected
  3. Update the user Pokemon’s HP bar
  4. Update the user Pokemon’s HP and check if user Pokemon fainted
  5. If it fainted, call processFaint which will toggle off the user Pokemon image
  6. If it didn’t faint, allow user to select attack to continue battle
  7. Update in-battle text for all of this accordingly

First, let’s add the data we need for our opponent Pokemon’s attacks and their damage values:

opponentAttacks: ["Tackle", "Iron Tail", "Rock Slide", "Slam"],
opponentAttackDamage: [15,40,50,25],

Now, let’s write the shell of a new function called processOpponentAttack:

processOpponentAttack: function(){

Next, let’s add a line to generate a random number which will be used to select which attack from opponentAttacks will be applied.

var random = Math.floor((Math.random() * 4) + 1)

Then, we do damage to the user’s HP by referencing a the damage of our random attack:

//do damage to user
this.userHP -= this.opponentAttackDamage[random-1]

We do random-1 because our random number is a range of 1–4 and the array is a range of 0–3.

Like before, we will have to adjust the HP fill by control with width percentage of the bar, just this time for the user’s HP bar. So let’s add a userFill in our data:

userFill: 100,

Also like before, we will subtract from our fill bar like so:

this.userFill -= (this.opponentAttackDamage[random-1])

Two things before we test!

First, we check to see if userFill is equal to or below 0. If it is, we give it a width of 0% (so it’s not negative). Else, we just concatenate userFill to a percentage so we can adjust the width of the user HP bar to reflect the damage done by the opponent:

if(this.userFill <= 0){
this.userHpBar.width = "0%"
} else{
this.userHpBar.width = this.userFill + "%"
}

Second, we just add our call to processOpponentAttack from processAttack:

processAttack: function(attack){
switch(attack){
case 1:
//handle scratch
this.opponentHP -= this.userAttackDamage[attack-1]
//edit if HP !== 0
this.opponentFill -= (this.userAttackDamage[attack-1)
if(this.opponentFill <= 0){
this.opponentHpBar.width = "0%"
} else{
this.opponentHpBar.width = this.opponentFill + "%"
}
if(this.checkOpponentHp()){
this.battleText = this.opponentPokemon + " fainted! Play again?"
this.processFaint(1)
} else if(this.checkOpponentHp() === false) {
setTimeout(() => {
this.battleText = "What will " + this.userPokemon + " do?"
}, 2000);

this.battleText = this.userPokemon + " used " + this.fightOptions[attack-1] + "!"

Sweet, let’s test!

We can see that Charizard is now taking damage!

There’s some issues that we will need to fix, but for now, let’s add a checkUserHp function:

checkUserHp: function(){

This function will be identical to checkOpponentHP except we switch to userHP:

checkUserHp: function(){
if(this.userHP <= 0){
//fainted
return true

} else{
//battle continues
return false
}
}

Next, let’s add the call in an if statement from processOpponentAttack so we can handle the result accordingly:

processOpponentAttack: function(){
//generate random number
var random = Math.floor((Math.random() * 4) + 1)
//do damage to user
this.userHP -= this.opponentAttackDamage[random-1]
this.userFill -= (this.opponentAttackDamage[random-1])
if(this.userFill <= 0){
this.userHpBar.width = "0%"
} else{
this.userHpBar.width = this.userFill + "%"
}
if(this.checkUserHp()){
this.processFaint(2)
} else if(this.checkOpponentHp() === false) {

//continue battle
}

}

What we do here is check to see if the user’s Pokemon should faint (HP equal to or below 0). If that’s the case, we call this.processFaint(2). If you recall, this will cause userAlive = false, which makes the user’s Pokemon image disappear.

Let’s test to see if this is working!

It is but we need to clean up.

If you notice, we are currently displaying the user Pokemon’s HP. On the right, it should be what the HP started as and on the left it should be set to 0 if the damage goes negative.

First, let’s add some data called startUserHP:

startUserHP: 100,

Now, let’s inject that into our HTML on the right side:

<h4 class="hp">{{userHP}}/{{startUserHP}}</h4>

Next, let’s set userHP equal to 0 in our processFaint so it’s not negative:

processFaint: function(pokemon){
this.fightOn = false
this.endOn = true;
if(pokemon === 1){
this.opponentAlive = false
} else {
this.userHP = 0
this.userAlive = false
}
}

That part should be fixed now!

We still need to resolve our in-battle text, but for now let’s update our code so that battleOptions is rendered for our options after the opponent attacks but there is no faint:

Because we are controlling our options with simple condition rendering, we just toggle off our fightOptions and toggle on our battleOptions after our check of the user’s HP and we see the battle should continue:

processOpponentAttack: function(){
//generate random number
var random = Math.floor((Math.random() * 4) + 1)
//do damage to user
this.userHP -= this.opponentAttackDamage[random-1]
this.userFill -= (this.opponentAttackDamage[random-1])
if(this.userFill <= 0){
this.userHpBar.width = "0%"
} else{
this.userHpBar.width = this.userFill + "%"
}
if(this.checkUserHp()){
this.processFaint(2)
} else if(this.checkOpponentHp() === false) {
//continue battle
this.fightOn = false
this.optionsOn = true

}
}

We should now see it working!

Pretty cool! Now, we have more clean up. We need to get our in-battle text and HP bars to be working smoothly.

To do this, we need to add a line to update our battle text in processOpponentAttack in the following location:

if(this.checkUserHp()){
//add battle text
this.processFaint(2)
} else if(this.checkOpponentHp() === false) {
//continue battle
this.battleText = this.opponentPokemon + " used " + this.opponentAttacks[random-1] + "!"
this.fightOn = false
this.optionsOn = true
}

Here, we will display what attack the opponent Pokemon selected.

Under each case in the switch statement in processAttack, we using setTimeouts to update our in-battle text. Currently, it outputs the attack the user selected and waits 2 seconds (2000 ms) to then display “What will [userPokemon] do? as seen in bold:

if(this.checkOpponentHp()){
this.battleText = this.opponentPokemon + " fainted! Play again?"
this.processFaint(1)
} else if(this.checkOpponentHp() === false) {
setTimeout(() => {
this.battleText = "What will " + this.userPokemon + " do?"
}, 2000);


this.battleText = this.userPokemon + " used " + this.fightOptions[attack-1] + "!"

To get the functionality we want, we just need to make some tweaks.

  1. In our processAttack function, we want this.battleText = this.userPokemon + “ used “ + this.fightOptions[attack-1] + “!” to be outputted first
  2. Then in our processOpponentAttack function, we want to output this.battleText = this.opponentPokemon + “ used “ + this.opponentAttacks[random-1] + “!”
  3. Lastly, we want to output this.battleText = “What will “ + this.userPokemon + “ do?” from the processAttack function

To do this, we will tweak the setTimeout for this.battleText = “What will “ + this.userPokemon + “ do?” so it is displayed last. So let’s change the setTimeout to 4 seconds (4000 ms). This means we have 4 seconds to output the attack the user Pokemon selected and the attack the opponent Pokemon selected, so 2 seconds for each.

Since the output of the user attack selected goes first, we leave it as is without a setTimeout wrapper.

Next, we just add a setTimeout of 2 seconds (2000 ms) around the call to processOpponentAttack which will display the attack selected by the opponent Pokemon. Because we are delaying the call, we also get the pause we need before we update the user’s HP bar.

Here are the changes we need (make sure to update this for every case in the switch statement):

if(this.checkOpponentHp()){
this.battleText = this.opponentPokemon + " fainted! Play again?"
this.processFaint(1)
} else if(this.checkOpponentHp() === false) {
setTimeout(() => {
this.battleText = "What will " + this.userPokemon + " do?"
}, 4000);


this.battleText = this.userPokemon + " used " + this.fightOptions[attack-1] + "!"
//call opponent attack function
setTimeout(() => {this.processOpponentAttack()
}, 2000);

}

So what happens? Let’s look at an example:

  1. Charizard used Scratch! ~ displays for 2 seconds and opponent HP bar updated
  2. Onyx used Tackle! ~ waits for the above line to finish, then displays for 2 seconds and user HP bar updated
  3. What will Charizard do? ~ if no faint, then displayed until user selects another attack. battleOptions will now be displaying

Quickly, let’s update the battle text in processOpponentAttack when the HP was checked and a faint of the user Pokemon has been discovered:

if(this.checkUserHp()){
//add battle text
this.battleText = this.userPokemon + " fainted! Play again?"
this.processFaint(2)
} else if(this.checkOpponentHp() === false) {
//continue battle
this.battleText = this.opponentPokemon + " used " + this.opponentAttacks[random-1] + "!"
this.fightOn = false
this.optionsOn = true
}

Getting so close! Unfortunately, we need to clean this up even further. If you test to get to a faint for both the opponent and the user, the opponent faint will update the battle text correctly but not the user faint.

The reason for this is it will return to this.battleText = “What will “ + this.userPokemon + “ do?” since there’s a setTimeout in this else if of processAttack, else if(this.checkOpponentHp() === false), which is where processOpponentAttack is being called.

It would seem that quick fix is to wrap an if statement in the else if statement where processOpponentAttack is called in processAttack:

else if(this.checkOpponentHp() === false) {

if(this.userAlive){

Ok I know this may be confusing so let’s walk through what’s happening currently and then explain what we hope the code above does.

  1. User selects attack → Charizard used Scratch! ~ displays for 2 seconds and opponent HP bar updated
  2. check for opponent faint, if not faint → continue to step 4
  3. check for opponent faint, if faint → Onyx fainted! Play again? ~ user should now be able to select endOptions (“Yes” or “No”) and we have yet to add functionality to handle selection
  4. processOpponentAttack called after 2 seconds of Charizard used Scratch!Onyx used Tackle! ~ displays for 2 seconds and user HP bar updated
  5. check for user faint, if not faint → What will Charizard do? ~ displayed until user selects another attack. battleOptions will now be displaying
  6. check for user faint, if faint → Charizard fainted! Play Again?~ displays for 2 seconds then overwritten by What will Charizard do? ~ as you can see here:
if(this.checkOpponentHp()){
this.battleText = this.opponentPokemon + " fainted! Play again?"
this.processFaint(1)
} else if(this.checkOpponentHp() === false) {
setTimeout(() => {
this.battleText = "What will " + this.userPokemon + " do?"
}, 4000);


this.battleText = this.userPokemon + " used " + this.fightOptions[attack-1] + "!"

Now, let’s look at what seems like a quick solution to this again:

else if(this.checkOpponentHp() === false) {

if(this.userAlive){

processOpponentAttack checks for the HP after an attack by calling checkUserHp which returns true (faint) or false (not faint). If there was a user faint, userAlive (which controls if user Pokemon image is displayed) will be set to false.

If userAlive is false, there was a faint. Therefore, we don’t want the following line to run:

setTimeout(() => {
this.battleText = "What will " + this.userPokemon + " do?"
}, 4000);

So…we we wrap it with an if statement with a condition if this.userAlive:

if(this.userAlive){

But…this logic is false.

Even though the setTimeout above takes effect last, the setTimeout is read first. Meaning, we are checking if our user Pokemon is alive before it is updated in processFaint. Let’s modify our logic.

if(this.checkOpponentHp()){
this.battleText = this.opponentPokemon + " fainted! Play again?"
this.processFaint(1)
} else if(this.checkOpponentHp() === false) {

setTimeout(() => {
this.processOpponentAttack()
}, 2000);


this.battleText = this.userPokemon + " used " + this.fightOptions[attack-1] + "!"

Now, the call to processOpponentAttack comes first. It will take effect 2 seconds which is enough time to output this.battleText = this.userPokemon + “ used “ + this.fightOptions[attack-1] + “!”.

After two seconds from the call to processOpponentAttack, the following will take effect:

if(this.userAlive){
setTimeout(() => {this.battleText = "What will " + this.userPokemon + " do?"
}, 2000);
}

If the user Pokemon is still alive, which is updated by our previous call to processOpponentAttack, the battle text will be updated to this.battleText = “What will “ + this.userPokemon + “ do?” after two seconds.

If we test this out, it should now be working:

Let’s talk out the events taking place.

  1. User selects attack → Charizard used Scratch! ~ displays for 2 seconds and opponent HP bar updated
  2. check for opponent faint, if not faint → continue to step 4
  3. check for opponent faint, if faint → Onyx fainted! Play again? ~ user should now be able to select endOptions (“Yes” or “No”) and we have yet to add functionality to handle selection
  4. processOpponentAttack called after 2 seconds of Charizard used Scratch!Onyx used Tackle! ~ displays for 2 seconds and user HP bar updated
  5. check for user faint, if not faint → What will Charizard do? ~ displayed until user selects another attack. battleOptions will now be displaying
  6. check for user faint, if faint → Charizard fainted! Play Again?~ displays and What will Charizard do? is not displayed

Our processAttack should now be:

processAttack: function(attack){
switch(attack){
case 1:
//handle scratch
this.opponentHP -= this.userAttackDamage[attack-1]
//edit if HP !== 0
this.opponentFill -= (this.userAttackDamage[attack-1])
if(this.opponentFill <= 0){
this.opponentHpBar.width = "0%"
} else{
this.opponentHpBar.width = this.opponentFill + "%"
}
if(this.checkOpponentHp()){
this.battleText = this.opponentPokemon + " fainted! Play again?"
this.processFaint(1)
} else if(this.checkOpponentHp() === false) {

setTimeout(() => {
this.processOpponentAttack()
}, 2000);

this.battleText = this.userPokemon + " used " + this.fightOptions[attack-1] + "!"
//call opponent attack function
setTimeout(() => {
if(this.userAlive){
setTimeout(() => {this.battleText = "What will " + this.userPokemon + " do?"
}, 2000);
}
}, 2000);
}
break;
case 2:
//handle fly
this.opponentHP -= this.userAttackDamage[attack-1]
//edit if HP !== 0
this.opponentFill -= (this.userAttackDamage[attack-1])
if(this.opponentFill <= 0){
this.opponentHpBar.width = "0%"
} else{
this.opponentHpBar.width = this.opponentFill + "%"
}
if(this.checkOpponentHp()){
this.battleText = this.opponentPokemon + " fainted! Play again?"
this.processFaint(1)
} else if(this.checkOpponentHp() === false) {

setTimeout(() => {
this.processOpponentAttack()
}, 2000);

this.battleText = this.userPokemon + " used " + this.fightOptions[attack-1] + "!"
//call opponent attack function
setTimeout(() => {
if(this.userAlive){
setTimeout(() => {this.battleText = "What will " + this.userPokemon + " do?"
}, 2000);
}
}, 2000);
}
break;
case 3:
//handle flamethrower
this.opponentHP -= this.userAttackDamage[attack-1]
//edit if HP !== 0
this.opponentFill -= (this.userAttackDamage[attack-1])
if(this.opponentFill <= 0){
this.opponentHpBar.width = "0%"
} else{
this.opponentHpBar.width = this.opponentFill + "%"
}
if(this.checkOpponentHp()){
this.battleText = this.opponentPokemon + " fainted! Play again?"
this.processFaint(1)
} else if(this.checkOpponentHp() === false) {

setTimeout(() => {
this.processOpponentAttack()
}, 2000);

this.battleText = this.userPokemon + " used " + this.fightOptions[attack-1] + "!"
//call opponent attack function
setTimeout(() => {
if(this.userAlive){
setTimeout(() => {this.battleText = "What will " + this.userPokemon + " do?"
}, 2000);
}
}, 2000);
}
break;
case 4:
//handle ember
this.opponentHP -= this.userAttackDamage[attack-1]
//edit if HP !== 0
this.opponentFill -= (this.userAttackDamage[attack-1])
if(this.opponentFill <= 0){
this.opponentHpBar.width = "0%"
} else{
this.opponentHpBar.width = this.opponentFill + "%"
}
if(this.checkOpponentHp()){
this.battleText = this.opponentPokemon + " fainted! Play again?"
this.processFaint(1)
} else if(this.checkOpponentHp() === false) {

setTimeout(() => {
this.processOpponentAttack()
}, 2000);

this.battleText = this.userPokemon + " used " + this.fightOptions[attack-1] + "!"
//call opponent attack function
setTimeout(() => {
if(this.userAlive){
setTimeout(() => {this.battleText = "What will " + this.userPokemon + " do?"
}, 2000);
}
}, 2000);
}
break;
}
}

ONE LAST STEP!

To wrap this up…finally…we just need to add some logic to handle if a user wants to start a new game (selecting “Yes” when asked to play again).

Let’s add another function called resetBattle:

resetBattle: function(){
//reset data to defaults before battle began
}

Next, let’s add the event handler that will call this function in our HTML:

<div v-if="endOn" id="endOptions">
<h4 v-on:click="resetBattle" class="battle-text-top-left">{{endOptions[0]}}</h4>
<h4 class="battle-text-top-right">{{endOptions[1]}}</h4>
</div>

To keep things simple, we just will call resetBattle on the click of endOptions [0] (“Yes”). Clicking endOptions [0] (“No”), will do nothing.

When we think about what needs to be reset, we can just look at our data. The data has values for before any manipulation, so we will just reset what we’ve manipulated to the default values like so:

resetBattle: function(){
//reset data to start new game
this.endOn = false;
this.fightOn = false;
this.optionsOn = true;
this.battleText = "What will Charizard do?"
this.userAlive = true
this.opponentAlive = true
this.userHP = 100
this.opponentHP = 100
this.userFill = 100
this.opponentFill = 100
this.userHpBar.width = "100%"
this.opponentHpBar.width = "100%"
}

We now have our fully functioning Pokemon battle!

See full code: http://codepen.io/mikemang/pen/zNJZYg/

See live demo: http://codepen.io/mikemang/live/zNJZYg

Future Work

First off, if you enjoyed this tutorial, then you will probably enjoy a new video course I will be releasing next week called Power Up With Pure CSS Images & Vue.js to Make Fun Apps. In this course, I will break down the basics of Vue.js and we’ll walk through more fun apps similar to this Pokemon Battle.

Second, this is an awesome side project to build upon. Here’s what you can add to our Pokemon battle:

  1. Fix logic so HP bar updates at proper rate if HP is not exactly 100
  2. Clean code by reducing unneeded lines, adding more functions, and/or adding more Vue instances
  3. Add transition effects for conditionally rendered elements
  4. Design your own Pokemon battle scene with CSS
  5. Allow for more Pokemon to be selected for a full battle
  6. Allow for items to be used
  7. Use logic used in Pokemon like super-effective, not-effective, critical hits, etc.
  8. Add way to level up Pokemon
  9. Add various scenes for a full Pokemon battle effect

Feedback is always appreciated, thanks for reading!

Cheers,

Mike Mangialardi

Coding Artist

Providing journeys for developers who see the web as their canvas

Michael Mangialardi

Written by

UI Developer in Southwest Virginia. Soli Deo Gloria.

Coding Artist

Providing journeys for developers who see the web as their canvas

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade