Lesson 6 - Arena with warriors in VB.NET
In the previous lesson, Warrior for the arena in VB.NET, we created the Warrior class. Our rolling die is already finished from the early lessons. In today's tutorial, we're going to put it all together and create a fully functioning arena. The tutorial is going to be simple and will help you get some more practice on working with objects.
We'll need to write some code that will manage our warriors and print
messages to the user. Of course, we won't put all this in the main module but
we'll keep things organized. We'll create an Arena
object where our
fight will take place. Module1.vb will only provide the necessary objects, and
the Arena object will take care of the rest. Let's add the last class to the
project - Arena.vb.
The class will be rather simple, it'll include three needed instances as the fields: the 2 warriors and the rolling die. These fields will be initialized from the constructor parameters. The class code will be as following (add comments accordingly):
Public Class Arena Private warrior1 As Warrior Private warrior2 As Warrior Private die As RollingDie Public Sub New(warrior1 As Warrior, warrior2 As Warrior, die As RollingDie) Me.warrior1 = warrior1 Me.warrior2 = warrior2 Me.die = die End Sub End Class
Let's think about the methods. We're definitely going to need a public method
to simulate the fight. We'll make the program output fancy and allow the
Arena
class to access the console directly. We've decided that the
printing will be done by the Arena
class since it makes sense here.
If the printing was performed by warriors, the design would be flawed since the
warriors would not be universal. We need a method that prints information about
the round and the warriors' health to the console. The damage and defense
messages will be printed with a dramatic pause so as to make the fight more
intense. We'll create a helper method for this. Let's start with the method that
renders the information screen:
Private Sub Render() Console.Clear() Console.WriteLine("-------------- Arena -------------- " & vbCrLf) Console.WriteLine("Warriors health: " & vbCrLf) Console.WriteLine("{0} {1}", warrior1, warrior1.HealthBar()) Console.WriteLine("{0} {1}", warrior2, warrior2.HealthBar()) End Sub
There's nothing to say here, you can also decorate the screen with colors if you want. Thanks to the Clear() method, which clears the console screen, we can create a nice visual layout instead of scrolling the text down all the time. The method is Private, it'll be used only within the class.
Let's create another Private method that will print messages with a dramatic pause:
Private Sub PrintMessage(message As String) Console.WriteLine(message) Thread.Sleep(500) End Sub
The Thread class allows us to work with threads. We use its Sleep() method
which puts the application thread to sleep for a given number of milliseconds.
We'll go over threads in detail in the last couple of courses. In order for it
to work, we'll have to add Imports System.Threading
at the
beginning of the Arena.vb file.
Now, let's move on to the fighting part. The Fight() method will be parameterless and won't return anything. There will be a loop inside calling the warriors' attacks in turns and printing the information screen with the messages. The method would look something like this:
Public Sub Fight() Console.WriteLine("Welcome to the Arena!") Console.WriteLine("Today {0} will battle against {1}!{2}", warrior1, warrior2, vbCrLf) Console.WriteLine("Let the battle begin...") Console.ReadKey() ' fight loop While warrior1.Alive() And warrior2.Alive() warrior1.Attack(warrior2) Render() PrintMessage(warrior1.GetLastMessage()) ' attack message PrintMessage(warrior2.GetLastMessage()) ' defense message warrior2.Attack(warrior1) Render() PrintMessage(warrior2.GetLastMessage()) ' attack message PrintMessage(warrior1.GetLastMessage()) ' defense message Console.WriteLine() End While End Sub
The code prints introductory lines and executes the fighting loop after the user presses any key. It's a while loop that repeats as long as both warriors are alive. The first warrior attacks his opponent and his attack internally calls the other warrior's defense. After the attack, we render the information screen. The messages about the attack and defense are printed by our PrintMessage() method which makes a dramatic pause after the printing. The same thing will happen with the other warrior.
Let's move back to Module1.vb. We create'll the needed instances and call the Fight() method on the arena:
Imports System.Threading
Public Class RollingDie
Private random As Random
Private sidesCount As Integer
Public Sub New()
sidesCount = 6
random = New Random()
End Sub
Public Sub New(sidesCount As Integer)
Me.sidesCount = sidesCount
random = New Random()
End Sub
Public Function GetSidesCount() As Integer
Return sidesCount
End Function
Public Function Roll() As Integer
Return random.Next(1, sidesCount + 1)
End Function
Public Overrides Function ToString() As String
Return String.Format("Rolling die with {0} sides", sidesCount)
End Function
End Class
Public Class Warrior
Private name As String
Private health As Integer
Private maxHealth As Integer
Private damage As Integer
Private defense As Integer
Private die As RollingDie
Private message As String
Public Sub New(name As String, health As Integer, damage As Integer, defense As Integer, die As RollingDie)
Me.name = name
Me.health = health
Me.maxHealth = health
Me.damage = damage
Me.defense = defense
Me.die = die
End Sub
Public Overrides Function ToString() As String
Return name
End Function
Public Function Alive() As Boolean
Return health > 0
End Function
Public Function HealthBar() As String
Dim s As String = "["
Dim total As Integer = 20
Dim count As Double = Math.Round((health / maxHealth) * total)
If count = 0 And Alive() Then
count = 1
End If
For i As Integer = 1 To count
s &= "#"
Next
s = s.PadRight(total + 1)
s &= "]"
Return s
End Function
Public Sub Attack(enemy As Warrior)
Dim hit As Integer = damage + die.Roll()
SetMessage(String.Format("{0} attacks with a hit worth {1} hp", name, hit))
enemy.Defend(hit)
End Sub
Public Sub Defend(hit As Integer)
Dim injury As Integer = hit - (defense + die.Roll())
If injury > 0 Then
health -= injury
message = String.Format("{0} defended against the attack but still lost {1} hp", name, injury)
If health <= 0 Then
health = 0
message &= " and died"
End If
Else
message = String.Format("{0} blocked the hit", name)
End If
SetMessage(message)
End Sub
Private Sub SetMessage(message As String)
Me.message = message
End Sub
Public Function GetLastMessage() As String
Return message
End Function
End Class
Public Class Arena
Private warrior1 As Warrior
Private warrior2 As Warrior
Private die As RollingDie
Public Sub New(warrior1 As Warrior, warrior2 As Warrior, die As RollingDie)
Me.warrior1 = warrior1
Me.warrior2 = warrior2
Me.die = die
End Sub
Private Sub Render()
Console.Clear()
Console.WriteLine("-------------- Arena -------------- " & vbCrLf)
Console.WriteLine("Warriors health: " & vbCrLf)
Console.WriteLine("{0} {1}", warrior1, warrior1.HealthBar())
Console.WriteLine("{0} {1}", warrior2, warrior2.HealthBar())
End Sub
Private Sub PrintMessage(message As String)
Console.WriteLine(message)
Thread.Sleep(500)
End Sub
Public Sub Fight()
Console.WriteLine("Welcome to the Arena!")
Console.WriteLine("Today {0} will battle against {1}!{2}", warrior1, warrior2, vbCrLf)
Console.WriteLine("Let the battle begin...")
Console.ReadKey()
' fight loop
While warrior1.Alive() And warrior2.Alive()
warrior1.Attack(warrior2)
Render()
PrintMessage(warrior1.GetLastMessage()) ' attack message
PrintMessage(warrior2.GetLastMessage()) ' defense message
warrior2.Attack(warrior1)
Render()
PrintMessage(warrior2.GetLastMessage()) ' attack message
PrintMessage(warrior1.GetLastMessage()) ' defense message
Console.WriteLine()
End While
End Sub
End Class
Module Module1
{VBNET_MAIN_BLOCK}
' creating objects
Dim die As RollingDie = New RollingDie(10)
Dim zalgoren As Warrior = New Warrior("Zalgoren", 100, 20, 10, die)
Dim shadow As Warrior = New Warrior("Shadow", 60, 18, 15, die)
Dim arena As Arena = New Arena(zalgoren, shadow, die)
' fight
arena.Fight()
Console.ReadKey()
{/VBNET_MAIN_BLOCK}
{/VBNET_OOP}
You can change the values to whatever you'd like. Here's what the program looks like at runtime:
Console application
-------------- Arena --------------
Warriors health:
Zalgoren [## ]
Shadow [ ]
Shadow attacks with a hit worth 19 hp
Zalgoren blocked the hit
The result is quite impressive. The objects communicate with each other, the health bar decreases as expected, the experience is enhanced by a dramatic pause. However, our arena still has two issues:
- In the fight loop, the first warrior attacks the other one. Then, the second warrior attacks back, even if he has already been killed by the first warrior. Look at the output above, at the end, Shadow attacked even though he was dead. The while loop terminated just after that. There are no issues with the first warrior, but we have to check whether the second warrior is alive before letting him attack.
- The second problem is that the warriors always fight in the same order so "Zalgoren" has an unfair advantage. Let's use the rolling die to decide who will start the fight. Since there will always only be two warriors, we can set the warriors' turns based off of whether the rolled number is less or equal to half of the number of die sides. Meaning that if it rolls a number less than 5 on a ten-sided die, the second warrior goes first, otherwise, the first one does.
So now we need to think about a way to swap the warriors depending on which
one goes first. It'd be very unreadable to add some conditions into the
While
loop. Since we know about references in VB.NET, let's just
create 2 variables that will contain warrior instances. Let's call them
w1
and w2
. At the start, we'll assign the values from
warrior1
and warrior2
to these variables as needed. If
the die condition mentioned above applies, we'll assign warrior2
to
w1
and vice versa, then the second warrior will begin. This way, we
won't have to change the loop code and it all remains nice and clear.
The updated version preventing the second warrior from attacking when he's already dead and letting the warriors start randomly can look like this:
Imports System.Threading
Public Class RollingDie
Private random As Random
Private sidesCount As Integer
Public Sub New()
sidesCount = 6
random = New Random()
End Sub
Public Sub New(sidesCount As Integer)
Me.sidesCount = sidesCount
random = New Random()
End Sub
Public Function GetSidesCount() As Integer
Return sidesCount
End Function
Public Function Roll() As Integer
Return random.Next(1, sidesCount + 1)
End Function
Public Overrides Function ToString() As String
Return String.Format("Rolling die with {0} sides", sidesCount)
End Function
End Class
Public Class Warrior
Private name As String
Private health As Integer
Private maxHealth As Integer
Private damage As Integer
Private defense As Integer
Private die As RollingDie
Private message As String
Public Sub New(name As String, health As Integer, damage As Integer, defense As Integer, die As RollingDie)
Me.name = name
Me.health = health
Me.maxHealth = health
Me.damage = damage
Me.defense = defense
Me.die = die
End Sub
Public Overrides Function ToString() As String
Return name
End Function
Public Function Alive() As Boolean
Return health > 0
End Function
Public Function HealthBar() As String
Dim s As String = "["
Dim total As Integer = 20
Dim count As Double = Math.Round((health / maxHealth) * total)
If count = 0 And Alive() Then
count = 1
End If
For i As Integer = 1 To count
s &= "#"
Next
s = s.PadRight(total + 1)
s &= "]"
Return s
End Function
Public Sub Attack(enemy As Warrior)
Dim hit As Integer = damage + die.Roll()
SetMessage(String.Format("{0} attacks with a hit worth {1} hp", name, hit))
enemy.Defend(hit)
End Sub
Public Sub Defend(hit As Integer)
Dim injury As Integer = hit - (defense + die.Roll())
If injury > 0 Then
health -= injury
message = String.Format("{0} defended against the attack but still lost {1} hp", name, injury)
If health <= 0 Then
health = 0
message &= " and died"
End If
Else
message = String.Format("{0} blocked the hit", name)
End If
SetMessage(message)
End Sub
Private Sub SetMessage(message As String)
Me.message = message
End Sub
Public Function GetLastMessage() As String
Return message
End Function
End Class
Public Class Arena
Private warrior1 As Warrior
Private warrior2 As Warrior
Private die As RollingDie
Public Sub New(warrior1 As Warrior, warrior2 As Warrior, die As RollingDie)
Me.warrior1 = warrior1
Me.warrior2 = warrior2
Me.die = die
End Sub
Private Sub Render()
Console.Clear()
Console.WriteLine("-------------- Arena -------------- " & vbCrLf)
Console.WriteLine("Warriors health: " & vbCrLf)
Console.WriteLine("{0} {1}", warrior1, warrior1.HealthBar())
Console.WriteLine("{0} {1}", warrior2, warrior2.HealthBar())
End Sub
Private Sub PrintMessage(message As String)
Console.WriteLine(message)
Thread.Sleep(500)
End Sub
Public Sub Fight()
' The original order
Dim w1 As Warrior = warrior1
Dim w2 As Warrior = warrior2
Console.WriteLine("Welcome to the Arena!")
Console.WriteLine("Today {0} will battle against {1}!{2}", warrior1, warrior2, vbCrLf)
' swapping the warriors
Dim warrior2Starts As Boolean = (die.Roll() <= die.GetSidesCount() / 2)
If warrior2Starts Then
w1 = warrior2
w2 = warrior1
End If
Console.WriteLine("{0} goes first!{1}Let the battle begin...", w1, vbCrLf)
Console.ReadKey()
' fight loop
While w1.Alive() And w2.Alive()
w1.Attack(w2)
Render()
PrintMessage(w1.GetLastMessage()) ' attack message
PrintMessage(w2.GetLastMessage()) ' defense message
If w2.Alive() Then
w2.Attack(w1)
Render()
PrintMessage(w2.GetLastMessage()) ' attack message
PrintMessage(w1.GetLastMessage()) ' defense message
End If
Console.WriteLine()
End While
End Sub
End Class
Module Module1
{VBNET_MAIN_BLOCK}
' creating objects
Dim die As RollingDie = New RollingDie(10)
Dim zalgoren As Warrior = New Warrior("Zalgoren", 100, 20, 10, die)
Dim shadow As Warrior = New Warrior("Shadow", 60, 18, 15, die)
Dim arena As Arena = New Arena(zalgoren, shadow, die)
' fight
arena.Fight()
Console.ReadKey()
{/VBNET_MAIN_BLOCK}
{/VBNET_OOP}
Now, let's take her for a spin!
Console application
-------------- Arena --------------
Warriors health:
Zalgoren [########### ]
Shadow [ ]
Zalgoren attacks with a hit worth 27 hp
Shadow defended against the attack but still lost 9 hp, and died
Congratulations! If you've gotten this far and have actually read through, you have the basis of object-oriented programming and should be able to create reasonable applications
In the next lesson, Inheritance and polymorphism in VB.NET, we'll explain object-oriented programming in further detail. We mentioned that OOP is based on three core concepts - encapsulation, inheritance, and polymorphism. We're already familiar with the encapsulation, the other two await you in the next lesson.