Saturday, December 10, 2005

Maru Batsu II

This is the follow up to the previous Maru Batsu post.
In the meantime I have put the intent project on rubyforge, and I am working hard to make it generally usable.

A few things have changed in the way I define the intents and I believe that I have made the system more complete, gradual to get into, easier to use and more structured that you can see in the Maru Batsu examples.

I invite you to follow the progress of the Intent project on Liquid Development.

Maru Batsu --- Installment II

In the previous installment I was defining the intents of Maru Batsu, the tic tac toe game.

intent_of_setting_up_the_game_properly

In an intent I simply make statements about the game.


I also start realizing that lots of setup code gets repeated and I wonder if I should abstract it out.. but I let it there for the time being, until it gets more bulky and I really feel the need to collapse it into an abstraction.

I start writing up
intent_of_recognizing_a_winner but I soon realize that to recognise a winner I will need to check a given board configuration.  How would I define that board?  I could create an empty board and play out a whole game on it, checking for a winner as I go along move after move, finally finding a winner in the end.

This seems to be a bit long winded, especially if i want to represent many winning board configurations.  What I need is to define a mid-game state for the tic tac toe game.  I opt for the possibility of injecting an externally defined board in the game and be able to start playing from there.  This way I will simply have to specify a certain board and check if it there is a winning configuration for a certain player.

I think that this is a nice example of how a 'testing' requirement, an intent - as I like to call it - shapes code along Dependency Injection patterns. 

Injecting a board will also
require to either state - or infer - which player's turn it is, check if the board is well-formed, etc.. 

All of this feels a bit too much for such a small game, so I decide to write out the whole game and later decide if I really need to evolve it there. 

Do I really need those constraints?  Why should I put these constraints if they are already clear and obvious in the mind of the developers that will build on top of Maru Batsu?  Do I really need explicit knowledge where convention-based knowledge and reasonable polite behaviour will do?  Probably not.  I stick with postponing the choice.

I start writing up the intent, specifying every single step

def intent_of_recognizing_a_winner
    game = Tictactoe()
   
    game.clean!

    game.player_A_ticks row_(1), col_(1)
    game.player_B_ticks row_(0), col_(2)
    game.player_A_ticks row_(1), col_(2)
 
As I get here I realize that I am not able to follow the evolution of the state of the board.  Maybe I really need a different way after all.  Yet I am going to continue until the end and refactor it later.  After making all moves I will simply make a statement on the configuration of the board.  I do not need it, but  it will serve  the purpose of communicating better with the readers of the intent.

I think this is a good case of breaking the symmetry - in theory you don't need it because it's superfluous, but adding it makes things somehow easier on the eye.

def intent_of_recognizing_a_winner
    game = Tictactoe()
   
    game.clean!

    game.player_A_ticks row_(1), col_(1)
    game.player_B_ticks row_(0), col_(2)
    game.player_A_ticks row_(1), col_(2)
    game.player_B_ticks row_(0), col_(2)
    game.player_A_ticks row_(1), col_(2)
    game.player_B_ticks row_(1), col_(0)
    game.player_A_ticks row_(0), col_(0)
    game.player_B_ticks row_(2), col_(2)
    game.player_A_ticks row_(0), col_(1)
    game.player_B_ticks row_(2), col_(1)

    game.state.should_be TictactoeBoard(
                            ["X","X","O"],
                            ["O","X","X"],
                            ["", "O","O"]
                         )

    game.is_game_complete?.should_be false 
    game.is_there_a_winner?.should_be false                    

    game.player_A_ticks row_(2), col_(0)
    game.state.should_be TictactoeBoard(
                            ["X", "X","O"],
                            ["O", "X","X"],
                            ["X", "O","O"]
                         )

    game.is_game_complete?.should_be true 
    game.is_there_a_winner?.should_be false                    
 
end

It works and it shows a whole game, but there are several things I feel uneasy about.  For a start this looks more like a use case than an intent, a simple statement about the game.  I also feel uneasy about

    game.is_game_complete?.should_be true 
    game.is_there_a_winner?.should_be false                    

I feel there is some redundant information there, but I cannot put my finger on it yet.  Since I am not sure on the illness, I also don't feel sure about the cure.  I believe that most of the times in development there aren't any lost opportunities.  If it's ugly, if it is a problem, if it gave me discomfort now, it will sick out later as well and at that point I might have some more information to deal with it.

I also notice that with those two statements I have described what a draw is, but I haven't played out any winning scenarios yet.

I think a little about copying and pasting this intent and making the modifications needed to define a winning board.  I am also thinking about creating a subroutine that takes the game through a number of moves so that I can recreate the game state on demand and then apply the extra moves and my statements of intent.

I am definetly not satisfied.  The board injection solution becomes more and more attractive.  I go for it.  I don't write the code, I just state that I should be able to set a board.  I replace the first part of the intent with the following

def intent_of_recognizing_a_winner
    game = Tictactoe()
   
    game.clean!

    game.set_board TictactoeBoard(
                        ["X","X","O"],
                        ["O","X","X"],
                        ["", "O","O"]
                   )

    game.state.should_be TictactoeBoard(
                            ["X","X","O"],
                            ["O","X","X"],
                            ["", "O","O"]
                         )
...

This is much much easier on the eye, and it doesn't force me to painstakingly specify every move from the beginning.  Of course, I could put a wrong board there, and I am not checking if the board is well formed (number of noughts equal to number of crosses minus one, not more than one winning configuration with no common cell, etc..).  I decide to not care.  I am writing the intents, and I am not going to state impossible configurations.

However it seems a bit verbose and redundant to keep stating that my board is indeed the board that I have injected.  I should create a further intent to check that board.set_up is working correctly.  After having stated that intent in one place I will remove the extra should bes in the code, confident that this fresh intent will catch any misbehaving code.  Nice, but once again I'll do it later when I really need it.

I clone the intent into a number of sibling intents to check different board configurations.

def intent_of_setting_up_the_game_in_the_middle
    game = Tictactoe()

    mid_game_board = TictactoeBoard(
                        ["X","X","O"],
                        ["O","X","X"],
                        ["", "O","O"]
                     )
   
    game.set_board mid_game_board
    game.state.should_be mid_game_board

    expect true, game.player_A_turn?   
    expect false, game.player_B_turn?   
   
    game.is_there_a_winner?.should_be false  
    game.is_game_complete?.should_be false 
end

The use of a variable to avoid the repetition of the board definition makes the code clearer, while still stating that I should get what I have set.

There are a few stylistic points that annoy me.  For example, I see

    game.set_board mid_game_board

and I realize it is more legible as

    game.set_board_to mid_game_board

This way you can actually read it out aloud.

I also don't like this form much:

    game.is_player_A_the_winner?.should_be false

I have two methods, one for player_A and one for player_B that are doing essentially the same thing.  I would prefer something like:

    game.winner?.should_be game.player_A
    game.winner?.should_be game.player_B
    game.winner?.should_be nil

these are more notes for further refactoring.  The code looks quite clean, but I already have quite a backlog of candidates for refactoring.

In the meantime I have added several intents.  This is a bit long winded.  Be patient and skim over them.  If you cannot skim over them while getting a sense of what they are stating, then I have done something wrong and I have not been shaping code after language.

def intent_of_setting_up_the_game_properly
    game = Tictactoe()
   
    game.player_A_symbol.should_be "X"                   
    game.player_B_symbol.should_be "O"                   

    game.clean!
    game.state.should_be TictactoeBoard(
                            ["","",""],
                            ["","",""],
                            ["","",""]
                         )

    expect true, game.player_A_turn?   
    expect false, game.player_B_turn?   
   
    game.is_there_a_winner?.should_be false                    
    game.is_game_complete?.should_be false 
end

def intent_of_setting_up_the_game_in_the_beginning_1
    game = Tictactoe()

    mid_game_board = TictactoeBoard(
                        ["","","" ],
                        ["","","X"],
                        ["","","" ]
                     )
   
    game.set_board_to mid_game_board
    game.state.should_be mid_game_board

    expect false, game.player_A_turn?   
    expect true, game.player_B_turn?   
   
    game.is_game_complete?.should_be false 
    game.is_there_a_winner?.should_be false                    
    game.is_player_A_the_winner?.should_be false
    game.is_player_B_the_winner?.should_be false
end

def intent_of_setting_up_the_game_in_the_middle_1
    game = Tictactoe()

    mid_game_board = TictactoeBoard(
                        ["X","X","O"],
                        ["O","X","X"],
                        ["", "O","O"]
                     )
   
    game.set_board_to mid_game_board
    game.state.should_be mid_game_board

    expect true, game.player_A_turn?   
    expect false, game.player_B_turn?   
   
    game.is_game_complete?.should_be false 
    game.is_there_a_winner?.should_be false                    
    game.is_player_A_the_winner?.should_be false
    game.is_player_B_the_winner?.should_be false
end

def intent_of_setting_up_the_game_in_the_middle_2
    game = Tictactoe()

    mid_game_board = TictactoeBoard(
                        ["X","X","O"],
                        ["O","X","X"],
                        ["", "", "O"]
                     )
   
    game.set_board_to mid_game_board
    game.state.should_be mid_game_board

    expect false, game.player_A_turn?   
    expect true, game.player_B_turn?   
   
    game.is_game_complete?.should_be false 
    game.is_there_a_winner?.should_be false                    
    game.is_player_A_the_winner?.should_be false
    game.is_player_B_the_winner?.should_be false
end

def intent_of_setting_up_the_game_in_the_end_with_a_draw
    game = Tictactoe()

    end_game_board = TictactoeBoard(
                        ["X","X","O"],
                        ["O","X","X"],
                        ["X","O","O"]
                     )
   
    game.set_board_to end_game_board
    game.state.should_be end_game_board

    expect false, game.player_A_turn?   
    expect false, game.player_B_turn?   
   
    game.is_game_complete?.should_be true
    game.is_there_a_winner?.should_be false                    
    game.is_player_A_the_winner?.should_be false
    game.is_player_B_the_winner?.should_be false
end

def intent_of_setting_up_the_game_in_the_end_with_player_A_winning
    game = Tictactoe()

    end_game_board = TictactoeBoard(
                        ["X","X","O"],
                        ["O","X","X"],
                        ["O","X","O"]
                     )
   
    game.set_board_to end_game_board
    game.state.should_be end_game_board

    expect false, game.player_A_turn?   
    expect false, game.player_B_turn?   
   
    game.is_game_complete?.should_be true
    game.is_there_a_winner?.should_be true
    game.is_player_A_the_winner?.should_be true
    game.is_player_B_the_winner?.should_be false
end

wow! done it.  Thanks for keeping up until here. 

The intents are not too bad, but there is much more that can be done, even if it might not be clear at this stage yet.

Also notice how much knowledge we have produced about the game, without writing a single executable statement yet.  Are we coding or are we doing analysis?  Are we analysing or are we writing up specifications?  Are these specifications or are they explorations?

The common language of development starts breaking down and becoming irrelevant as we continue along our journey.





0 Comments:

Post a Comment

Links to this post:

Create a Link

<< Home