Introduction#
If you haven't completed the introductory task, please refer to the previous article Getting Started with Installation.
Sparring Sorcerers is the first task in the Think Cairo series on data structures and algorithms in Cairo. It mainly focuses on ownership and array operations. You need to write a function that simulates a battle between two teams of sorcerers.
In the file sorcerer.cairo, the sorcerers are defined with attributes such as attack, health, and talent. There are two methods, new and with_talent, for creating instances of sorcerers. There are also methods for getting the attack and health values, and a bool value indicating failure if the health is 0.
The files sorcerer_duel.cairo and sorcerer_battle.cairo need to be implemented by ourselves.
Part I: Simple Duels#
In the first part, we need to write the duel function in sorcerer_duel.cairo, which takes two sorcerers as parameters: duel(ref sorcerer1: Sorcerer, ref sorcerer2: Sorcerer)
. Each round, sorcerer1 attacks first and sorcerer2 attacks second. The battle stops if either sorcerer's health reaches 0. There is no return value.
We can use the loop{}
construct in Cairo, which is similar to the while
loop in other programming languages. This part is quite easy. In the loop, we decrease the health of the opposing sorcerer by the attack value of the current sorcerer. The loop terminates if one sorcerer's health reaches 0. Note that the health value should not become negative.
Part II: The Talent Mechanic#
In the second part, we need to add talents to the sorcerers. There are three new talents: Venomous, which increases the attack by 1 each round; Swift, which only inflicts 1 damage if the opponent's attack is less than 4; and Guardian, which can block the first instance of damage.
Note the use of the ref
keyword in the function fn duel(ref sorcerer1: Sorcerer, ref sorcerer2: Sorcerer)
. With ref
, we can directly modify the parameters passed into the function, thereby changing the state outside the function. Refer to https://book.cairo-lang.org/ch04-02-references-and-snapshots.html?highlight=snapshot#mutable-references for more information.
My approach is to assign the attack value to a mutable variable named damage
, like this: let mut damage1 = sorcerer1.attack;
. We can also read the attack value using sorcerer1.attack()
, because the Sorcerer
struct implements the method fn attack(self: @Sorcerer) -> u8 { *self.attack }
. The @
symbol is used to get a snapshot, and *
is used to convert the snapshot to a new variable. However, we need to import use src::sorcerer::SorcererImpl;
.
First, we check for Swift. For example, if sorcerer2 has the Swift talent and sorcerer1's attack is greater than 4, damage = 1
.
Then we check for Guardian. If sorcerer2 has the Guardian talent, we skip modifying the health during the battle and change it to Talentless.
Finally, we check for Venomous and directly modify sorcerer.attack
by adding 1 to it (not damage
).
Note the comparison method for enums, the use of the mut
keyword, and other parts that are similar to general programming languages. The condition statement in the loop needs to end with a semicolon, otherwise there will be an error saying that the condition cannot be used in the loop.
Part III: Team Battles#
The third part is about team battles. We need to create two arrays, team1
and team2
, and send out the first sorcerer from each team to battle. If a sorcerer's health reaches 0, we switch to the next sorcerer. As for the order of attacks, you need to refer to the test case test/battle_test_3.cairo
.
Similarly, the function battle(ref team1: Array<Sorcerer>, ref team2: Array<Sorcerer>)
uses ref
for the team arrays. The subsequent operations will directly modify the arrays.
The basic idea is to send out the first sorcerer from each team to duel in each battle. If a sorcerer's health reaches 0, we switch to the next sorcerer. The battle ends when either team has no more sorcerers or when the health of the battling sorcerers reaches 0.
However, directly modifying the elements in the arrays, even with ref
, will not take effect. We need to use pop_front
to remove the elements from the arrays, modify them, and then add them back.
There are many details that need to be adjusted based on the test cases. After passing the local tests, there will be more rigorous tests online, so the code needs to be improved.
If the test cases cannot be passed, it is recommended to use debugging to find the reasons step by step.
Conclusion#
The content that truly poses a challenge is beginning. I hope you can remain patient. If you get stuck, take a look at other documents, such as https://github.com/starkware-libs/cairo/tree/main/corelib/src and https://book.cairo-lang.org/. With perseverance, you will achieve great results. Keep up the good work!