Showing posts with label classes. Show all posts
Showing posts with label classes. Show all posts

Thursday, February 2, 2017

Review of 'Toy Robot' by Ryan Bigg



Toy Robot by Ryan Bigg (@ryanbigg) is an excellent walkthrough for a budding Rubyist, especially one that has worked through Learn Ruby the Hard Way. (As I have!)

Bigg is a veteran Ruby developer and prominent member of the Ruby and Rails community in Melbourne, Australia.  His blog is a valuable resource for both novices and experts.  The book weighs in at just over 100 pages and strikes a balance between technical jargon and clear, simple prose.  His descriptions of the rationale for his approach shed light on the code blocks in the book, and are very helpful to a novice like me.

The book describes itself as "A Walkthrough for The Toy Robot", which is misleadingly accurate, for the term "walkthrough" conjures up images of a many happy hours spent on old-school click-and-point adventure games. (King's Quest comes to mind)

This is not a "walkthrough" in that sense.  Rather than simply prescribe the steps for building a Ruby solution to the toy robot problem, Bigg's book takes the reader on a full test-driven-development journey of writing tests, making them fail, making them work, and making them fail again, until every class, module, method, and function has been thoroughly vetted.
The book demands that you see for yourself how to outline the problem, write tests for each-and-every detail of the software and allow testing (and especially test failures) to guide you through the process.

Having spent 18 tight pomodoros (20-minute blocks), i.e. six hours on this book, I cannot now duplicate every detail of the book's solution to the problem, but whereas before it was insurmountable, it is now assailable.  My solution would not be as tight, but it would get there.  The lion's share of my learning came not from memorising what the book does, but rather from poring over the output of the failed tests to understand what went wrong, and sometimes why my test failures were different from the book's.

For this reason, I strongly recommend that you NOT copy and paste, but rather type out every line of code for yourself, and run all the tests to see the results.  I promise you will come to see the HOW and WHY of Bigg's elegant solution to the toy robot problem.

The book is available in PDF, EPUB, and Mobi formats and is available for as little as $5, although I hope you will find it in your heart to spend at least $10 in recognition of this author's fine work.

Rating: 5/5

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!)

Tuesday, January 17, 2017

User Testing Update



I've had to hit the books again to improve my understanding of variables, classes, and methods.

I've been re-writing my text adventure game so that every aspect of it is tested by the Rakefile.  This means that I have had to extrapolate the logic of the enter() method in each class, so that a test can be written for it.

The most interesting parts of the game involve somewhat complex conditionals, written as case conditionals, and for some reason it took me a while to understand how to pass on a variable to the method containing a case conditional.  Now that I look at it again, it's very simple, but c'est la vie.

Anyway, the re-written game is nearing completion and the latest code is up on Github.

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?


Saturday, December 24, 2016

Note to Self about Modules in Ruby

Make sure to include the name of the module in the method:

module GameDictionary

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

Everything else is rather straightforward, but for some reason I keep on missing that detail.  I'll just keep on drilling it until it sinks in.

In other words, I'll keep on crashing until I learn how to fly this damn aeroplane :)


Defining Scenes in a Text Adventure Game

In my text adventure game, the user starts outside in meadow, and eventually gets inside a six-storey tree castle with a staircase that runs from the ground floor to the fifth.

Here is a map of the world:


The scene of greatest interest here is the Staircase, for it is actually not a scene at all, but rather a method within the HouseScene subclass of Scene.  That is, the user can leave any room and go back to the landing of the staircase on the floor of that room.

For example, the bedroom is on the fourth floor, and if the user elects to leave the bedroom, she will return to the fourth floor landing of the staircase.

Using 'relative_require' to separate functions, classes, instantiation

My goal is to create a main.rb file that does nothing but instantiate the classes and execute their methods.  As you can see below, this is possible, at least in a simple application, but is this good practice?

It feels neater to separate classes, functions, etc; but then again, this might be akin to separating vocabulary sheets by parts of speech -- tidy, but impracticable.

It might be better practice to group blocks of code that work on the same portion of the program.  Please let me know what works in your experience.

A simple function:

## demo_function.rb

def hello_world()
  puts "Hello world!"
end

A class that uses this function as a method:

## demo_class.rb

require_relative 'demo_function.rb'

class Demo
  def enter()
    hello_world()
  end
end

Finally, an instance of the class that executes its method:

## demo_main.rb
require_relative 'demo_class'

a_class = Demo.new()
a_class.enter()

Output:

Hello World! 


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 ...