Nerd For Tech
Published in

Nerd For Tech

Creating a Basic CLI Game with Object Inheritance and Join Class (Repl included)

So it was time for me to start my first project in the Flatiron School. Besides having to either scrape a site or use an API and the end result being a Command Line Interface (CLI) application, there weren’t any other limitations. But I didn’t even know about the scraping/API requirements when I first started but that’s not the point of this post.

I’ve always loved games and I remember reading somewhere that one of the earliest RPGs was a CLI game! I was probably super sleep-deprived when I messaged my cohort-lead (a.k.a teacher) that I wanted to do a CLI RPG. At that time, I don’t think we were even done with the Object-Oriented-Programming (OOP) in Ruby section of the course.

So then, I was so confused as to how I would be able to incorporate health points and mana points into the game. How would I record how many monsters I have killed? How do I attack the monster? How will the monster attack me? How will everything relate to each other? Who will keep track of all this?

I was so stressed.

Object Inheritance

So then a few days passed and we finished learning about Object Relationships and I was still confused.

But hey, I know I am the type of person to learn through many, many, MANY mistakes. Without even knowing what I doing, I started coding my game.

Now, I thought that was a pretty nice idea. Just look at how organized it is! But then I thought about wanting to create more Races in the future. A game with only one Race would be pretty boring, don’t you think? So then would I have to copy and paste all of those instance variables over to my other classes? Yikes.

But then, we progressed to Object Inheritance in the course. I WAS SAVED. Ruby is such a wonderful language for newbies like me. Just like the name suggest, an object can inherit traits from another object! How neat is that?

How does it work? Let’s create the parent class first. This is the class that will have all the basic traits that I want my children classes (or sub classes) to have. I will create the parent class in Race.rb in the /lib folder.

I want each child class to have different health and mana, so I decided to just leave those traits out of the initialize method. But I still kept the attr_accessor because I simply cannot be bothered to copy and paste that whole block of text every time I want to create a new class.

So now that we have a parent class, we can give birth to a child class. How do we inherit those lovely traits? Let’s see!

I will create some children classes. One will be in the Elf.rb file in the /lib folder. Another will be called Giant.rb and will also exist in the /lib folder. This way, when I invoke on require_relative on the parent class, it wouldn’t be very far up the directory.

This is the Elf.rb file.

This is the Giant.rb file.

Let’s break this down!

This line means that the current file I’m on (whether it be Elf.rb or Giant.rb) will need another file (that is in a relative path to the current file I’m in) in other to work. Ruby is smart enough to know that I meant a Ruby file so that was why I left the file name as just ‘Race’. And I did not need to indicate any directories because they all exist in the same folder! Lovely!

Anyway, if I do not require ‘Race’ and I try to run my Elf.rb file, I will receive an error in RED RED text along the lines of:

Let’s break this down next.

This means that Elf is a child of Race. Elf will inherit EVERYTHING that currently exists in the Race class. This includes the all of the attr_accessor macros and setting of the instance variables inside initialize.

But currently the Race class does not have any health or mana set! Well that’s because I want each Race to start with different health and mana! That was why I manually set the health and mana. I used self.health= and self.mana= because I had defined customized health and mana setter methods. Thus, instead of just setting the health instance variable and mana instance variable, I invoked on self.health= and self.mana= to set the health and mana, respectively.

Let’s look at the Giant class.

Writing ‘super’ inside the initialize method will tell Ruby that I want the Giant class to inherit everything that the Race class initialize method has.

Now why did I set self.happiness = 4 when the Race class already has the happiness instance variable set? It was no mistake. I want the Giant class to be happier than the rest of the sub-Races so I decided to overwrite the Race class’s setting of @happiness. Again, I created a customized setter method for the happiness attribute, so that why I invoked on that setter method instead of simply setting the attribute to 4.

I could also do it with the @evilness instance variable if I wanted to. All I have to do is:

So whenever I felt the need to change some of the stats that were originally set in the parent Race class, all I had to do was to overwrite it in the child class.

Using Object Inheritance is super helpful in creating classes that share common methods and common stats (which would set as instance variables).

Another example would be these Race methods. Every time I create a new sub-Race class, the child will inherit all of these methods! So now I don’t even have to copy and paste. Isn’t that way more efficient than what I had before?

So now onto the problem with keeping track of the player’s attacks and the monster’s attacks. Who keeps track of what?

So each sub-Race class exists in their own file. Elf doesn’t know anything about a monster existing. That also means a monster also doesn’t know about the existence of the Elf class. However, they can “interact” with each other through the CLI class. I called my CLI class, Rubytopia. Rubytopia will be the class joining everything (all of my monsters and all of my sub-Races)! Rubytopia is not only my CLI class but also my “join” class.

What is a join class?

Well, that’s where I create methods for my sub-Race classes and monsters so that they will have battles! Only in the Rubytopia class will they be able to fight each other. Simply put, a join class joins everything. Everything can have interact with each other’s information through the join class.

Let me show you!

So I have a monster called Sorcerer. This is part of the Sorcerer.rb file. The Sorcerer only knows about itself right now. Can’t really do much by itself upon initialization. The same is also true for the Elf class.

This is part of my Rubytopia file.

So now a Sorcerer and an Elf exist in Rubytopia. Rubytopia has an attribute called @monster, which keeps track of the current monster object. Later, I can access the monster object’s health to see whether or not the battle sequence should continue. Likewise, Rubytopia also has a @player attribute that keeps track of the current player, which is a sub-Race object, and the player’s health and mana. Now for the coding-magic!

The following code exists in Rubytopia.

So even without me showing what the self.player_turn_choice method is, you can already guess that it’ll probably just be options for the player. The same goes with self.monster_or_player_death and self.monster_turn.

Just to make my code more compact, I used one code to check for the monster and player’s health, hence the method name monster_or_player_death. (But that’s just my preference.) The join class has access to the information of both the monster and the sub-Race because they exist as attributes of Rubytopia (also the CLI class and the join class). Rubytopia can access data such, such as each object’s health, through the object’s reader method! Coding magic!

So this is one of those instances where the monster and sub-Race can interact with each other. Although never directly, they are able to exist in the same method because of the join class. If I wanted, I could also create a method that would let them talk to each other! That’s the magic of the join class. It gives life to two classes (at the same time) that otherwise wouldn’t know about each other’s existence!

while/until loops

The player_turn method shown above was one of the last methods I wanted to tackle because of my shallow understanding of the while loop.

So why did I use it in my method?

Well, because it gets the job done. I want the player to stay in battle (handled by player_turn) as long as self.monster_or_player_death is false. Imagine having to code a simple game of encountering monsters without a loop! I would have to hard code every encounter of a monster! That just sounds… painful.

Let’s go through the while loop code snippet again but this time we shall look at the function of the while loop instead of the class interactions.

The player_turn method will loop through the block that is within the while loop as long as the counter is less than 7. Every time it goes through the loop, it will perform:

  • self.player_turn_choice
  • check if self.monster_or_player_death is true (will break the loop if it’s true)
  • self.monster_turn
  • check if self.monster_or_player_death is true (will break the loop if it’s true)
  • add 1 to local variable counter and reassigns the value of counter
  • go back to the start of the loop.

So one of two things will break this loop under a normal and functioning loop:

  • counter gets to 7 by the end of the loop
  • self.monster_or_player_death returns true.

At first I only checked self.monster_or_player_death once in the loop. Then I played through the game to test for bugs and saw that the monster attacked me even though it had 0 health! That was when I realized I had to put in another check after the self.monster_turn.

While loops are definitely helpful but it might take a few tries to get the method going but it’s definitely worth using!

Now for the until loop!

I used an until loop in the start method. The start method is what gets an instance of Rubytopia to start running. You can imagine there to be other methods right before we get to the until loop, but those aren’t important right now.

All this code is saying is that until the self.over? method returns a truthy value, self.event_generator (the method that randomly generates a monster of other events) will keep on executing.

Although you may not know the exact code of over?, you can guess from its name that it tests for whether or not the game is over.

Using the until loop is so so SO much better than manually telling my program to generate an event a certain amount of times.

Conclusion

Phew! That was a lot to read!

Object relationships can be really confusing at first. I had no idea how I would build out my game at first. To be honest, I didn’t even know how the join class would work with my game. The very first day I was trying to build out my game, I was trying to get my Elf class to access data about the monsters. Some of my brain cells died from that messy thought.

It really does take time to learn about how to optimize each and every object relationship in your project(s)! It took me a lot of trial and error to see what was working and what was absolute madness. Towards the beginning of the journey, I created instance variables for @player_health and @player_mana in my Rubytopia class. It was as if I had completely forgotten about getter methods.

I knew I had to use loops to get my game generating monsters but I was just so scared to use them! I didn’t want to be stuck in an infinite loop! But I am so glad that I reviewed my knowledge on loops so that my vision for my game became a reality.

As I was writing my game, I learned that it’s super important to take a break when your brain is tired… or when you’re hungry. Your brain just can’t function if you’re not energized. So having a well-rested brain definitely helps with tackling any project!

Here is the repl to my game for anyone interested! Please click the green play button to continue.

I am excited to deepen my knowledge about object relationships as I continue to create more projects in my Flatiron journey!

--

--

NFT is an Educational Media House. Our mission is to bring the invaluable knowledge and experiences of experts from all over the world to the novice. To know more about us, visit https://www.nerdfortech.org/.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store