Limelight Tutorial: Tic Tac Toe Example 1

Posted by Paul Pagel Tue, 30 Sep 2008 01:59:00 GMT

Welcome to a Limelight production. I am going to go through a step by step introduction to limelight development using a tic tac toe game as an example. So, lets get started. I am going to create the directory structure and open it up in a text editor.

$ mkdir tictactoe
$ cd tictactoe
$ mate .

Now I need to set up Limelight. You can just download the gem.

$ jruby -S gem install limelight

We can start by creating the props.rb file in the tictactoe directory. The props.rb file defines the structure of your application. A prop is named after the theatre metaphor. We are going to use them to define what our scene’s physical structure look like. We can start with a simple screen with an empty board with the nine cells we need for a tic tac toe game. Lets create a spec directory to write a test for the props we are going to create.

$ mkdir spec
$ mkdir spec/props

Now for the spec. In the spec directory, we can name our spec props_spec.rb. We want to check that there is a cell on the scene. Here is the first test. NOTE: To be able to run the test, you will need the spec_helper.rb in your spec directory (not the props directory). You can copy it from the sample application.

require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")

describe "Props" do
  include PropSpecHelper
  before(:each) do 
    setup_prop_test
  end

  it "should have cell_0_0" do
    @scene.find("cell_0_0").should_not be(nil)
  end
end

and when we run it (You can copy the Rakefile from the sample application as well, if you want to have a specs task),

$ jruby -S rake spec

we get the failure

1) Errno::ENOENT in 'Props should have cell_0_0' No such file or directory - File not found - /Users/paulwpagel/Desktop/tictactoe/props.rb /Users/paulwpagel/Desktop/tictactoe/spec/spec_helper.rb:18:in `initialize' /Users/paulwpagel/Desktop/tictactoe/spec/spec_helper.rb:18:in `setup_prop_test' /Users/paulwpagel/Desktop/tictactoe/./spec/props/props_spec.rb:6: Finished in 0.063 seconds 1 example, 1 failure

So, lets create the props.rb file in the project root. Now we should get the error.

1) 'Props should have cell_0_0' FAILED expected not nil, got nil /Users/paulwpagel/Projects/tictac/./spec/props/prop_spec.rb:12: Finished in 0.089 seconds 1 example, 1 failure

Each of the props accepts a block of code your can give options/structure to. We can open the props.rb file and add a cell with the id of “cell_0_0” to make this test pass.

main do
    board do
        cell :id => "cell00"
    end
end

And the test passes. Lets make sure we have the rest of the id’s while we are at it. Here is a more exhaustive spec.

it "should have cells" do
  @scene.find("cell00").should_not be(nil)
  @scene.find("cell01").should_not be(nil)
  @scene.find("cell02").should_not be(nil)
  @scene.find("cell10").should_not be(nil)
  @scene.find("cell11").should_not be(nil)
  @scene.find("cell12").should_not be(nil)
  @scene.find("cell20").should_not be(nil)
  @scene.find("cell21").should_not be(nil)
  @scene.find("cell22").should_not be(nil)
end

And it fails in a similar manner. Lets expand our props.rb file to make the test pass.

main do
  board do
    cell :id => "cell00"
    cell :id => "cell01"
    cell :id => "cell02"
    cell :id => "cell10"
    cell :id => "cell11"
    cell :id => "cell12"
    cell :id => "cell20"
    cell :id => "cell21"
    cell :id => "cell22"
  end
end

And it passes. However, it is all ruby code, so I can leverage ruby functions to help me out. Lets remove the duplication.

main do
    board do
        3.times do |row|
          3.times do |col|
            cell :id => "cell#{row}#{col}"
          end
        end
    end
end

Much better. Lets now move on to the styles. Nothing will show up without a few styles. I create a styles.rb file in the project root and filled it with some simple content. In Limelight, styles refer to how a prop is aesthetically displayed on the screen. Here is an example which defines the size and gives a border to the board and the cells.

board {
  width 152
  height 152
  border_width 1
  border_color "black"
}
cell {
  width 50
  height 50
  border_width 1
  border_color "black"
}

We should be able to start up Limelight and see the board. We start Limelight like: (From the tictacctoe directory)

$ jruby -S limelight open .

and there is your first Limelight screen. Pretty easy, and all ruby code. Lets make it more interesting. Let us make it such that if you click on one of the squares, the square shows the ‘X’ mark denoting the first move.

First we create a directory called players. Inside go the players, which contain the actions and behavior of the props for a Limelight scene (the controllers).

$ mkdir players

We want to now make a player for the cell prop. We create a file inside of the players directory called cell.rb. The file will start with a definition by looking like:

module Cell

end

We define all players in modules of the same name as the file and prop, by convention. This allows Limelight to include this behavior when it needs it. You can specify specific mappings between the props and its players, but we don’t need to do that here. So, let’s make the cell more interesting. When we click on the cell, we want it to make a large ‘X’ mark. Lets start by creating a spec for the behavior.

I created a new directory for the players spec

$ mkdir spec/players

We have to add the players directory to the ruby search path, so I added the following line to the spec_helper.

$: << File.expand_path(File.dirname(FILE) + "/../players")

My spec is going to find the prop that was clicked on and make that prop display an ‘X’, denoting the first move. Here is what my first spec looks like (I call it cell_spec.rb):

require File.expandpath(File.dirname(FILE) + "/../spechelper")
require 'cell'

describe Cell do include Cell attr_accessor :id

it "should make first move an X" do @id = "cell00" @cell_one = Limelight::Prop.new @scene = MockScene.new @scene.register("cell00", @cell_one) self.stub!(:scene).and_return(@scene) mouse_clicked(nil) @cell_one.text.should == "X" end end

Which provides the feedback when run:

F 1) NoMethodError in 'Cell should make first move an X' undefined method `mouse_clicked' for # /Users/paulwpagel/Projects/tictac/spec/players/cell_spec.rb:14: Finished in 0.007423 seconds 1 example, 1 failure

If you have seen a rSpec specification before, this should look syntactically familiar. Before we move on to making the test pass, let us take closer look at a few aspects.

@id = “cell_1_1” - This line is setting the id of the imaginary prop that the players behavior will be executed against. @scene = MockScene.new - This creates a scene to mock out. The scene will be explained later, but for this test we are going to use the find method on scene to find our props. @cell_one = MockProp.new - Create a mock prop that will turn to ‘X’ when clicked @scene.register(“cell_1_1”, @cell_one) - We are giving the scene the mock prop, so the find method will find it by its id. mouse_clicked(nil) - Simulates a mouse_click on the cell. It takes an event, but we don’t care about that yet, so lets just pass in nil.

All right, time to make this test pass. Lets open up the cell.rb player and see what we need done to make the test pass.

module Cell
  def mouse_clicked(event)
    cell_prop = scene.find(id)
    cell_prop.text = "X"
  end
end

Run the test again, no failures. We needed to find the prop on the screen which we are concerned about. We do this by calling find on a method scene, which will give us any prop by its unique identifier. We are looking for the id of the element we clicked, and then we set the text of that element to ‘X’, which makes the test satisfied.

Now, we can run the application from the root directory.

$ jruby -S limelight open . 

If we click on the box that is displayed, a small ‘X’ should appear in the upper right corner.

Congratulations, that is your first piece of Limelight behavior. However, this is not very interesting yet. Lets take it the next step and make the tic tac toe game work. I am going to create a lib directory to hold the game model.

$ mkdir lib
$ mkdir spec/lib

And before I write my first spec, I am going to add the new lib directory to the ruby search path by adding the following line to the spec_helper (It is already in the example spec_helper.rb, you don’t need to add it).

$: << File.expand_path(File.dirname(FILE) + "/../lib")

So, here is what my first spec looks like:

require File.expandpath(File.dirname(FILE) + "/../spechelper")

require 'game'

describe Game do it "make a move in the middle square" do game = Game.new game.move(1, 1) game.mark_at(1, 1).should == "X" end end

To make it pass, we need to create a game class in the lib directory and give it this code.

class Game
  def move(row, column)
  end

def mark_at(row, column) return "X" end end

And we can follow the test driving of the model to make the game class. I have already done this, and you can download the models in the sample application. Lets move past that back to the players and hook up the game.

I am going to create a file init.rb in the root directory. The init.rb class gets loaded up by Limelight when you start the application. We want to create a new game and have a way to keep it in memory for the other classes to use. Here is what the spec looks like in a init_spec.rb in the spec directory:

require File.expandpath(File.dirname(FILE) + "/spechelper")
require "game"

describe "init" do it "should create new game on initialization" do game = mock('game') Game.shouldreceive(:new).andreturn(game) Game.should_receive(:current=).with(game)

require File.expand_path(File.dirname(__FILE__) + "/../init")

end end

The simplest way to start that is to have a current_game class variable. Here is the code for the init.rb.

$: << File.expand_path(File.dirname(FILE) + "/lib")

require "game" Game.current = Game.new

I add the lib directory to the ruby search path so the Limelight application would know what a game is when I require it.

Now we need to plug the game model into the cell player. Lets change the spec we made earlier to make the first move depending on the game model. Here is the new version.

it "should make first move in a game" do
  @id = "cell00"
  @cell_one = Limelight::Prop.new
  @scene = MockScene.new
  @scene.register("cell00", @cell_one)
  self.stub!(:scene).and_return(@scene)

game = mock('game') Game.shouldreceive(:current).andreturn(game) game.should_receive(:move).with(0, 0) game.shouldreceive(:mark).andreturn("X")

mouse_clicked(nil)

@cell_one.text.should == "X" end

I am mocking out the game model and passing the values from the id into the game’s move method. Here is the code that makes this pass.

module Cell
def mouse_clicked( event)

game = Game.current
x, y = get_coordinates
game.move(x, y)
cell_prop = scene.find(id)
cell_prop.text = game.mark

end

private ################################

def get_coordinates() x = id[(id.length - 1)..(id.length - 1)].to_i y = id[(id.length - 3)..(id.length - 3)].to_i return x, y end

end

Minus the ugly string manipulation, it is a pretty straight forward approach. Now we should be able to start up the application and click on any of the squares and make some moves. There are 2 things left to do for this demo. We need to make sure that a player can not move on a square that is already occupied, and we need to display a winner. So for the first task, we need to write a spec to have some kind of feedback to the players that the move is invalid. Let’s add this spec to the props_spec file.

it "should have message center for feedback to the user" do
  @scene.find("messagecenter").shouldnot be(nil)
end

Nice and simple. Here is the new props.rb file.

main do
  board do
    3.times do |row|
      3.times do |col|
        cell :id => "cell#{row}#{col}"
      end
    end
  end
end

messagecenter :id => "messagecenter"

Now lets write a spec for the cell_spec to make sure that the move is valid, else we display a message in the message center to the user they must move somewhere else. Here is the spec.

it "should display in the message center if the space is occupied." do
  @id = "cell00"
  @cell_one = Limelight::Prop.new
  @message_center = Limelight::Prop.new
  @scene = MockScene.new
  @scene.register("cell00", @cell_one)
  @scene.register("messagecenter", @messagecenter)
  self.stub!(:scene).and_return(@scene)

game = mock('game') Game.shouldreceive(:occupied?).with(0, 0).andreturn(true)

mouse_clicked(nil)

@message_center.text.should == "This space is occupied, please move in an unoccupied square" end

Same as before, with a new prop added. Here is the new cell.rb file.

module Cell

def mouse_clicked( event) game = Game.current x, y = get_coordinates if game.occupied?(x, y) messagecenter = scene.find("messagecenter") message_center.text = "This space is occupied, please move in an unoccupied square" else game.move(x, y) cell_prop = scene.find(id) cell_prop.text = game.mark end end

private ################################

def get_coordinates() x = id[(id.length - 1)..(id.length - 1)].to_i y = id[(id.length - 3)..(id.length - 3)].to_i return x, y end

end

Simple if, makes it all work. Lets remove the duplication in the specs. Here is the new spec file.

require File.expandpath(File.dirname(FILE) + "/../spechelper")
require 'cell'

describe Cell do include Cell attr_accessor :id

before(:each) do @id = "cell00" @cell_one = Limelight::Prop.new @scene = MockScene.new @message_center = Limelight::Prop.new @scene.register("messagecenter", @messagecenter) @scene.register("cell00", @cell_one)

self.stub!(:scene).and_return(@scene)
@game = mock('game', :occupied? => false) 
Game.should_receive(:current).and_return(@game)

end

it “should make first move in a game” do @game.should_receive(:move).with(0, 0) @game.shouldreceive(:mark).andreturn(“X”)

mouse_clicked(nil)

@cell_one.text.should == "X"

end

it “should display in the message center if the space is occupied.” do @game.shouldreceive(:occupied?).with(0, 0).andreturn(true)

mouse_clicked(nil)

@message_center.text.should == "This space is occupied, please move in an unoccupied square"

end

end

Much better. Now lets do the case of a winner. Here is the spec for the cell.

it "should display there was a winner in the message center" do
  @game.should_receive(:move).with(0, 0)
  @game.shouldreceive(:iswinner?).and_return(true)

mouse_clicked(nil)

@message_center.text.should == "Player X has won the game, congratulations" end

And here is the new cell.rb

module Cell

def mouse_clicked( event) game = Game.current x, y = get_coordinates if game.occupied?(x, y) message_center.text = "This space is occupied, please move in an unoccupied square" else game.move(x, y) cell_prop = scene.find(id) cell_prop.text = game.mark messagecenter.text = "Player #{game.mark} has won the game, congratulations" if game.iswinner?

end

end

private ################################

def get_coordinates() x = id[(id.length - 1)..(id.length - 1)].to_i y = id[(id.length - 3)..(id.length - 3)].to_i return x, y end

def message_center return scene.find(“message_center”) end

end

Now we can finish off the application by adding new game functionality, or even a computer player that can not be beaten! However, before I let you go, we have to add some styles to the message center and pretty up the board to make it look better. To find a comprehensive list of the styles supported in Limelight, go here(http://limelightwiki.8thlight.com/index.php/Style_Attributes). Here is a new version of the styles.rb.

main {
  width "100%"
  horizontal_alignment "center"
}
board {

width 152 height 152 border_width 1 border_color "black" } cell { width 50 height 50 border_width 1 border_color "black" } messagecentercontainer{ top_margin 100 width "100%" horizontal_alignment "center" } message_center { width 300 height 100 roundedcornerradius "10" border_color "black"
border_width 2 padding 5

}

There was one change to the props.rb file, to wrap the message_center in a prop called message_center_container. Also, notice the pretty rounded corners. Easy to do.

Here is the props.rb

main do

board do 3.times do |row| 3.times do |col| cell :id => "cell#{row}#{col}" end end end end

messagecentercontainer do messagecenter :id => "messagecenter" end

Happy Limelight coding!

How Paul started coding

Posted by Paul Pagel Wed, 13 Aug 2008 22:01:00 GMT

Tagged by Micah Martin

How old were you when you started programming.

I was around 12

How did you get started programming.

Whenever I would visit my grandmother growing up, one of the highlights was she had space invaders on her computer. Once, my Uncle Don saw me captivated by space invaders and decided to show me how computer games/application are written. He pulled up a command prompt in DOS and started writing QBasic code to create a simple application. I was amazed that it was so easy to talk to a computer. I wanted to do it myself.

What was your first language?

BASIC

What languages have you used since you started programming?

Java, C++, C#, SAS, JavaScript, CSS, HTML, Ruby, C, Basic, Python, Assembly Language, Visual Basic, SQL, ASP

What was the first real program you wrote?

In school I wrote a Jabber client in C#. I was impressed by the drag and drop rich client tools in Visual Studio. That lasted about a day.

What was your first professional programming gig?

Working for a client project with Object Mentor. The first team I was on included Micah Martin, David Chelimsky, Tim Ottinger, Dave Astels, Craig Demyonovich, James Grenning, and Dean Wampler. I had more than just a few mentors to learn from. It was an awing experience for me seeing how great coders code.

If there is one thing you learned along the way that you would tell new developers, what would it be?

Play well with others. It takes teams working well together to create most meaningful software.

What’s the most fun you’ve ever had programming?

Working with the 8th Light team on a project. Many times seeing the requirements for a story, I will say to myself, “can that even be done”? After seeing creative solutions from the team, it has taught me if you are working with good people and good tools, there are only endless solutions. The most amusing thing to learn about programming is if something isn’t all ready there, invent it.

Tagging: Adam Wonak, Jim Suchy, Bob Payne

Apprenticeship month one report

Posted by Paul Pagel Tue, 01 Jul 2008 01:14:00 GMT

8th Light has an apprenticeship program whereby an 8th Light craftsman will mentor an individual for three months. During that time, the craftsperson becomes responsible for a single apprentice. I am one month into a mentorship with an apprentice. This blog is about observations I have made about myself as a mentor.

Curiosity

He asks me questions that I wouldn’t ask myself. I had him do an exercise about Law of Demeter , to find Law of Demeter violations in my code base. He found something like:

Law of Demeter

“Ted Williams”.to_s.strip.upcase

He was counting the periods, which is what I explained is often good measurement of Law of Demeter violations. Yet this is not a Law of Demeter violation. Each one of those methods returns itself (a string) in a changed state. It is not a string of implementation details- this example is just changing a single object. The pivotal piece of information is that all of these methods on string return the string itself. Also, is anything inside of Ruby core language capable of Law of Demeter? It is unlikely to change, and is there a problem being coupled tightly to your language?

I sometimes get desensitized to the original premises of good development. When I first learn something, I remember the example of the rule more than the value of the rule. The value of learning about Demeter is rooted in encapsulating logic in such a way to hide implementations from an object’s clients. When I first learned about Demeter, all I could do was point out violations. The more I developed, the more it was internalized as a guide for a higher development idea. I want to keep my modules as decoupled as possible. However, that notion only came with development experience. Now I don’t think in terms of violations or non-violations of Demeter when I read a piece of code. It is just ingrained in my developmental context that I should develop my modules in such a way that they are autonomous. This weeds out most Law of Demeter violations by itself. Bringing these premises back up in my development as a craftsman gets my curiosity started. Like a good song or piece of writing, every time I come back to it, I take something different from it, because I am in a state of constant change.

Teaching

Sometimes I forget how important teaching is to the craftsmanship model, because I am still an extreme novice at it. It is one of the steps involved with internalizing development skills. When I pair with my mentor, I am constantly amazed at how he skips steps in the development rational process. Instead of following a linear thought pattern to a solution, it seems there is a giant shortcut in the rational processes I go through. That gap is a lookup table of solutions in his head. It appears so easy and effortless. Teaching is part of a formula that has given him a development context that is superior to mine. The more I am able to teach, the more I internalize the ideas about development.

There is a baseball story from about a year ago. Greg Maddux was warming up before a game and his catcher exclaimed to the pitching coach, “I bet I could catch him with my eyes closed.” Well, after much effort, they convinced Maddux to give it a shot. The catcher was going to call his pitch, then close his eyes. When the ball was about to hit his mitt, the pitching coach with his eyes open was going to yell “now”, whereupon the catcher would squeeze his glove on the ball. Well, on the third try, the catcher caught the ball. The degree of difficulty of the exercise is incredible, but Greg Maddux had spent so many years internalizing the mechanics of his craft that he could effortlessly hit the catcher’s mitt. There probably are only a few pitchers in the long history of baseball who could match that exercise. I am not anywhere near that kind of skill in development, but that is my goal. When you get the mechanics of your craft to be intuition, it frees your mind to think about and solve larger problems. I have been fortunate to spend time with enough great developers to see this mastery in action.

Communication

For me, what is special about teaching is the commitment of thoughts to sound. Forcing something outside of my internal monologue always changes it, even if it is a small change. When I explain something, I often have to explain it multiple ways (this is telling of my communication skills, not of my apprentice’s learning skills). So, the more I am forced to answer questions on the spot, the better I get at quickly thinking through a question and giving a good answer. This dialogue is another one of the mental exercises which translates into my everyday development. It makes me a better pair programmer, better presenter, and better writer. Being able to accurately explain a problem and a solution using the language of software development is a very important tool in being a successful craftsman, and a tool that I fight uphill to improve upon. When I go to a talk at a conference and the presenter hits the problem and solution perfectly, it is a wonderful experience. The same is true when a team member can explain a solution in such intuitive terms that everyone gets it immediately.

Apprenticeship for me is as much about internalizing and expanding my own skill set as it is about expanding my apprentice’s.

Older posts: 1 2 3 ... 5