Connect Four Basics - acanyon/RubyTuesdays-ConnectFour GitHub Wiki

Connect Four is a test driven rails project (similar to the ruby koans). Getting started will be a lot easier if you've done a little Ruby and looked at some Rails before jumping in. The first two chapters in Michael Hartl's rails tutorial are a pretty thorough overview. If you run into an error message somewhere, try googling it to see if anyone else has had the same problem.

What Connect Four has out of the box:

  • A Game model and controller
    • With a complete datamodel and method stubs
    • Without any method implementations (those are for you to write!)
  • Model and controller unit tests (in rspec)
  • Views for the frontend

First Steps

Here we're assuming that you've already installed ruby & rails and you've forked and cloned the project to your machine. If that didn't make sense or you don't think you've done that, head to Setup Ruby (OS X and Windows).

Open up your terminal or command line and cd into your Connect Four directory. The first steps for any rails project (that isn't starting from scratch) are the following commands:

$ bundle install        // installs any gems that you need, which are listed in your Gemfile
$ rake db:migrate       // runs any migrations you have and sets up your database and datamodel

Next, try running the tests.

$ rspec spec/          // runs all the tests in the project

If that doesn't work, your test database probably needs to be set up.

$ rake db:test:prepare
$ rspec spec/

You'll notice a lot of red text in the output - those are all the broken tests that will be fixed when you finish connect four!

My First Test Fix

Using a test-based project gives you a way to check that you're coding in a bug-free way. Having a project with tests and not much else means you can use the tests to figure out how to write the code.

We're going to walk through fixing one of the tests just to get going. Run this rspec command:

$ rspec spec/models/game_spec.rb -e 'if column is full'

What happened? We told rspec to run just the one example (-e 'if column is full) in the game_spec.rb file. game_spec.rb is the file that holds all the tests for the Game model, and these are the tests that we're interested in. Take a look at the [rspec](RSpec Tips & Tricks) page to get an idea of what's going on. It's okay if it doesn't all make sense right now.

So let's take a look at some code. Open up game_spec.rb in your text editor and find the line that says it 'if column is full' do. This is the example/test that we're going to take a closer look at.

  it 'if column is full' do
    game.board[column] = Array.new(Game::NUM_ROWS).map{'red'}

    game.changed?.should be_false
    expect {game.make_move(column, 'blue')}.to raise_error(ArgumentError)
    game.changed?.should be_false
  end

What's going on here? The it 'if column is full' do.....end block defines this particular example. All the code inside it what was run during that long rspec command. Let's go through line-by-line.

    game.board[column] = Array.new(Game::NUM_ROWS).map{'red'}

The right hand side makes an array of length NUM_ROWS full of the string 'red': {'red', 'red', 'red', 'red', 'red', 'red'} and assigns it to the left hand side. NUM_ROWS is defined in the game.rb file at the top. map() is a ruby function; remember you can always google for more explanations.

On the left hand side, game is defined a few lines up (look for let(:game)) by FactoryGirl, a test helper. column is defined the same way at the very top. game.board[column] in all is one of the column of the board property of a Game object. So now we've filled one of the board columns to capacity.

    game.changed?.should be_false

Right here we call the changed? method on the game and check that it is false.

    expect {game.make_move(column, 'blue')}.to raise_error(ArgumentError)

Now we call the expect rspec method which takes a block and then asserts that something happens. In this case we give it game.make_move(column, 'blue') and expect an error, specifically an ArgumentError.

    game.changed?.should be_false

And again we check that the game object didn't change.

So what do we have to do to get this to pass? Go back and look at your terminal and look at what it says is the failure. There should be something like this:

Failure/Error: expect {game.make_move(column, 'blue')}.to raise_error(ArgumentError)
       expected ArgumentError but nothing was raised

That's telling us the line that failed and a little bit of explanation about why. Here we see that we expected the make_move method to raise (or throw) an ArgumentError exception but it didn't. So now we have to get make_move to throw that error. What error message do we need? If we go back and look at the title of the example it will tell us.

Now open game.rb in your editor and find the make_move method. From the previous paragraph we know that we need to raise an ArgumentError about the column being full when the column is full. Do a quick google for 'argumenterror ruby' to get some background. Hopefully at least one of the links tells you how to use it, so if you want to, go ahead and try it.

The simplest way to fix the test is to simply raise the error no matter what:

def make_move(column, player)
  raise ArgumentError
end

Run the test and see what happens! It passes! But that's really ambiguous error message. What about adding a better message?

def make_move(column, player)
  raise ArgumentError, 'column full'
end

Okay, better, but now there's no way to get past the error AND we accidentally fixed other tests. How do we only raise the error if the column passed in is actually full?

def make_move(column, player)
  raise ArgumentError, 'column full' unless ...  // try to do it yourself! 
end

Do some thinking about what code to replace the ... with to make this work. Remember that the column is an array, so you can call any Array methods on it. And we know how many rows there are supposed to be (it's stored at the top of the file).

.

.

.

.

def make_move(column, player)
  raise ArgumentError, 'column full' unless column.size < NUM_ROWS
end

Hooray!