Lesson 6 - Arena with warriors in Swift
In the previous lesson, Warrior for the arena in Swift, we created the Warrior
class.
Our rolling die is already finished from the early lessons. In today's Swift 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.swift
file but we'll keep things organized. We'll create an
Arena
object where our fight will take place.
main.swift
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.swift
.
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):
class Arena { private var warrior1: Warrior private var warrior2: Warrior private var die: RollingDie init(warrior1: Warrior, warrior2: Warrior, die: RollingDie) { self.warrior1 = warrior1 self.warrior2 = warrior2 self.die = die } }
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:
func render() { print("\n \n \n \n \n \n \n \n") print("-------------- Arena -------------- \n") print("Warriors health: \n") print("\(warrior1) \(warrior1.healthBar())") print("\(warrior2) \(warrior2.healthBar())") }
There shouldn't be anything to explain here. Because there's no reasonable
way to clear the output, we'll just add enough empty lines to see only the
current output. 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 func printMessage(_ message: String) { print(message) sleep(1) }
The code is self-explanatory except the sleep()
method which
makes the application thread sleep for a given number of seconds.
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:
func fight() { print("Welcome to the Arena!") print("Today \(warrior1) will battle against \(warrior2)!\n") print("Let the battle begin...") readLine() // fight loop while warrior1.alive() && warrior2.alive() { warrior1.attack(enemy: warrior2) render() printMessage(warrior1.getLastMessage()) // attack message printMessage(warrior2.getLastMessage()) // defense message warrior2.attack(enemy: warrior1) render() printMessage(warrior2.getLastMessage()) // attack message printMessage(warrior1.getLastMessage()) // defense message print(" ") } }
The code prints introductory lines and executes the fighting loop after
pressing Enter (to confirm the input for readLine()
).
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 to main.swift
. We'll create the needed instances and
call the fight()
method on the arena:
// creating objects let die = RollingDie(sidesCount: 10) let zalgoren = Warrior(name: "Zalgoren", health: 100, damage: 20, defense: 10, die: die) let shadow = Warrior(name: "Shadow", health: 60, damage: 18, defense: 15, die: die) let arena = Arena(warrior1: zalgoren, warrior2: shadow, die: die) // fight arena.fight()
You can change the values to whatever you'd like. Here's what the program looks like at runtime:
-------------- 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 Swift, let's just
create 2 variables that will contain the 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.
Let's also get rid off the readLine()
warning. The method
returns String?
(Optional
) but we don't care about the
return value. Let's assign it to "_" which is what you mark this situation with
in Swift. It's useful for both us and another programmers as well because we can
quickly see that we don't need this value any further and the code becomes more
readable this way.
The updated version preventing the second warrior from attacking when he's already dead and letting the warriors start randomly can look like this:
func fight() { // The original order var w1 = warrior1 var w2 = warrior2 print("Welcome to the Arena!") print("Today \(warrior1) will battle against \(warrior2)!\n") // swapping the warriors let warrior2Starts = (die.roll() <= die.getSidesCount() / 2) if (warrior2Starts) { w1 = warrior2 w2 = warrior1 } print("\(w1) goes first! \nLet the battle begin...") // fight loop while w1.alive() && w2.alive() { w1.attack(enemy: w2) render() printMessage(w1.getLastMessage()) // attack message printMessage(w2.getLastMessage()) // defense message if (w2.alive()) { w2.attack(enemy: w1) render() printMessage(w2.getLastMessage()) // attack message printMessage(w1.getLastMessage()) // defense message } print(" ") } }
Now, let's take her for a spin!
-------------- 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 Swift, 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.