Showing posts with label oop. Show all posts
Showing posts with label oop. Show all posts

Monday, January 23, 2017

Project Complete!


I have finished re-writing my text adventure game, Tree House Prince, available here: (https://github.com/clockworkpc/tree-house-prince)

It is a very simple game, but I am proud of all the things I learned to get it into this shape.

The game demonstrates my grasp of conditionals, classes, inheritance, and automation testing.  Every scene in the game has full module testing in Rakefile; the integration testing has to be done manually at this stage.  (I will learn about that soon!)

Wednesday, January 18, 2017

Getting the Hang of Rakefile!

There seems to be an error every step of the way, but, inch by inch, I'm getting there.


Monday, January 2, 2017

Getting started with Rakefile and testing

Up to chapter 47 of Learn Ruby the Hard Way, and I have uploaded version 0.01 of my little text adventure, Treehouse Prince.

https://github.com/clockworkpc/treehouseprince/tree/master/skeleton

The purpose of working on this is to familiarise myself with the fundamentals of testing.  As I put back all the components of the game, I shall get to see how the Rakefile saves me running the game manually a thousand times over.


Monday, December 26, 2016

Ruby Project Skeleton Generator on Gist!

Exercise 46 of Learn Ruby the Hard Way gives instructions for manually creating a project skeleton.  So I decided to do what I have done many times before in BASH and Python, and automate this task with a pretty Ruby script.

A few thoughts:
  1. Ruby's handling of files and folders is delightfully straightforward.
  2. Ruby handles strings elegantly.
  3. This was originally written as a straight script, but then I refactored it with object-oriented programming in mind:
    1. One Class (Project)
    2. The main components of the class broken down into methods.
    3. Names of folders and files are stored in an array and a hash.
    4. Blocks of text are stored using Squiggly HEREDOC

Using a string as a class variable

My script relies on a single argument in the form $stdin user input:

class Project
  def initialize()
    puts "What do you want to call this project?\n"
    print "> "

    $user_input = $stdin.gets.chomp


The script is organised into a single class that contains consecutive methods.

The simplest way I have found to utilise the user input is to declare it in each method.

  def define_folders()
    project_name = $user_input


  def create_gemfile()
    project_name = $user_input

However, this is repetitive.

What would be a better way?


Wednesday, December 21, 2016

Objected-oriented Programming, Part 1

I am working on my first text-adventure game as part of Exercise 43 of Learn Ruby the Hard Way.  I have defined a simple class called Scene:

class Scene
  def enter()
    puts "This scene is not yet configured. Subclass it and implement enter()."
    exit(1)
  end
end

In other words, the only thing that "Scenes" have in common is that the player "enters" them.  What she does and whither she goes thereafter has to be defined. 

Within the game there are broadly speaking two kinds of scenes:
  1. Scenes outside the house
  2. Scenes inside the house

Scenes Outside the House

 
The very limited movements afforded at the start of the game serve to illustrate the point that not a lot of thought went into (or had to go into) defining how you navigate the first part of the map. 
 
Meadow => River
Meadow => Front door

River => Front door 
Front door => riddle => Ground floor of the house.

Scenes Inside the House

This is where things get tricky.  All the rooms in the house are connected by a staircase, which means that when you leave one room, you have access to all the others by means of the staircase.

At first I defined the method for getting up the stairs from the ground floor entrance composed of the following elements:
  • Text for the player
  • Declared value for floorNumber
  • While loop
  • Case conditional that calls the engine.
def enter()

"""
    You are at a spiral staircase.  There is a sign on the landing:

    0. Ground Floor: Entrance to the castle

    1. First Floor: Machine room

    2. Second Floor: Kitchen

    3. Third Floor: Library

    4. Fourth Floor: Royal Bedroom

    5. Penthouse: Study

    6. Exit the castle and go back to the meadow.

    Where would you like to go to?

    """
    currentFloor = 0
    while currentFloor == 0
      print "> "

      floorSelection = $stdin.gets.chomp.downcase()

      case floorSelection
      when /0/, /ground/, /entrance/
        if currentFloor == 0
          puts "You are already here.  Try another floor."
        elsif currentFloor != 0
          puts "You descend the stairs to the ground floor."
          return 'spiral_staircase'
        else
          puts "Something has clearly gone wrong."
        end
      when /1/, /first/, /machine/
        if currentFloor < 1
          puts "You ascend the stairs to the machine room."
          currentFloor == 1
          return 'machine_room'
        elsif currentFloor > 1
          puts "You descend the stairs to the machine room."
          currentFloor == 1
          return 'machine_room'
        elsif currentFloor == 1
        puts "You are already here.  Try another floor."
        else
          puts "There has clearly been a mistake somewhere."
        end
      when /2/, /second/, /kitchen/
        if currentFloor < 2
          puts "You ascend the stairs to the kitchen."
          currentFloor == 2
          return 'kitchen'
        elsif currentFloor > 2
          puts "You descend the stairs to the kitchen."
          currentFloor == 2
          return 'kitchen'
        elsif currentFloor == 2
        puts "You are already here.  Try another floor."
        else
          puts "There has clearly been a mistake somewhere."
        end
      when /3/, /third/, /library/
        if currentFloor < 3
          puts "You ascend the stairs to the library."
          currentFloor == 3
          return 'library'
        elsif currentFloor > 3
          puts "You descend the stairs to the library."
          currentFloor == 3
          return 'library'
        elsif currentFloor == 3
        puts "You are already here.  Try another floor."
        else
          puts "There has clearly been a mistake somewhere."
        end
      when /4/, /fourth/, /bedroom/
        if currentFloor < 4
          puts "You ascend the stairs to the bedroom."
          currentFloor == 4
          return 'bedroom'
        elsif currentFloor > 4
          puts "You descend the stairs to the bedroom."
          currentFloor == 4
          return 'bedroom'
        elsif currentFloor == 4
        puts "You are already here.  Try another floor."
        else
          puts "There has clearly been a mistake somewhere."
        end
      when /5/, /fifth/, /study/, /penthouse/
        if currentFloor < 5
          puts "You ascend the stairs to the study."
          currentFloor == 5
          return 'study'
        elsif currentFloor > 5
          puts "You descend the stairs to the study."
          currentFloor == 5
          return 'study'
        elsif currentFloor == 5
        puts "You are already here.  Try another floor."
        else
          puts "There has clearly been a mistake somewhere."
        end
      when /6/, /leave/, /exit/, /back/, /meadow/
        if currentFloor != 0
          puts WordWrap.ww "You descend the stairs, walk out the door, and keep going until the you reach the edge of the meadow."
          currentFloor == 0
          return 'meadow'
        elsif currentFloor == 0
          puts "You walk out the door, and keep going until the you reach the edge of the meadow."
          return 'meadow'
        else
          puts "There has clearly been a mistake somewhere."
        end
      else
        puts "I don't understand your request"
      end

    end
end

But copy-pasting all of this would violate the DRY principle, so I thought about how to write it better:
  • All 'Scenes' use the enter() function.
  • All the rooms are 'Scenes'.
  • All the rooms are 'Scenes' that require the same function for selecting a floor.
  • Ergo, all the rooms are 'Scenes' use a function that could be defined as 'selectFloor()'.
  • However, every room is located on a particular floor, which changes whether the player goes up or down the stairs, or is already at that floor.
  • Ergo, every 'House Scene' needs to inherit the function 'selectFloor()' but with a variable for its floor, i.e. 'selectFloor(floorNumber)'
 First, I created a sub-class of 'Scene' called 'HouseScene':
class HouseScene < Scene

  def floorSelection()
    puts "This scene is not yet configured.  Subclass it and implement floorSelection()."
    exit(1)
  end

end


But remember that the floorSelection function is a while loop, so it needs a constant to hold true while it runs.  The obvious candidate is the floor number, so the function takes an argument and starts like this:
  def floorSelection(floorNumber)
    puts "Filler text for the moment"
    currentFloor = floorNumber
    while currentFloor == floorNumber


From there it's simply a matter of pasting the rest of the floorSelection(floorNumber) function into the sub-class 'HouseScene'.

Then, for each room, call the functions:
  • enter()
  • floorSelection()
If the player leaves the room, she will go back to the landing of the staircase and choose again whither she wants to go.

This slightly more complex code brings me finally to the point where testing my program becomes a drag.  Until now, I could effortlessly run a straightforward program from start to finish; now, I have many potential routes to follow, and testing them all will take exponentially more time as I add games and puzzles to each room.

In my next post I will share my first experience of testing and Rake files in Ruby.

Thursday, December 15, 2016

Sometimes one must be a night owl to see the light

It's 03:08 and I am finally starting to make sense of object-oriented programming.  It's not that I didn't understand it conceptually, but the little game I'm making works and I'm beginning to understand -- not merely comprehend -- why it works.

Good night.


Image source

1,050 hours

It took me 13 working days to complete my first 100 "work" pomodoros as a Junior Software Tester at Profectus Group.  Much of ...