Red Green Repeat Adventures of a Spec Driven Junkie

Object Oriented Design Practice: Tic-Tac-Toe

Motivation

After helping organize NYC’s Code Retreat this year, I wanted to do some deliberate practice on my own. I love organzing events for other programmers, but I feel I don’t get hands on enough with the event since I am one of the organizers and constantly distracted with keeping things running smoothly.

So, when a friend told me about coding practice that he does: write a console based unbeatable Tic-Tac-Toe, I took the challenge. I didn’t follow the Code Retreat format, I just wanted to practice on my own and get it done as best I can.

Unbeatable Tic-Tac-Toe

With very little info, I just quickly scratched out a working version of Tic-Tac-Toe which had:

  • multiple play modes! (human vs. human, computer vs. computer, and each combo of human and computer)
  • unbeatable computer play!

What more can you ask for from Tic-Tac-Toe?!

Instead of bogging down you with code details, I will go over high level details of the classes and functions I used, and also discuss the some of the design goal I had in mind.

Classes

These are the top level classes I have in the first attempt:

Functions in each class

  • Engine
    • initialize
    • loser?
    • winner?
    • score_move
    • best_move
    • open_positions
    • opponent_of
    • positions_with
    • next_player
    • draw?
    • play_next_move
    • print_board
    • entry
    • new_board
    • next_player_char
    • menu
    • game_over?
  • ‘ronin’ methods
    • read_char
    • human_x_computer_o
    • computer_x_human_o
    • debug_mode
    • computers_mode
    • humans_mode
    • game
    • start_game

Design Goals

Unbeatable Play

The intention for the Engine class was to have a class which “drove” the algorithm for unbeatable computer play. The Engine class would take in the current game state of the board and return the next best move on the board. All the class methods associated with it are to support this goal. Nothing more, nothing less.

For the “ronin” methods: those are all the methods that did not support the Engine, so I just threw them out into their own class. Part of me was lazy, another part was focused on getting unbeatable play above all else. (Hence the engine had its own class and was the main focus.)

Testing

Next to this, I wanted things easy to test. The most popular algorithm for unbeatable tic-tac-toe is the min-max algorithm, which is recursive.

The easiest way I found to test a recursive method is to externalize all the state, so it can be tested from certain points. So, given a certain board setup, a certain move is expected. I took a functional approach.

At the same time, I also applied this to all other methods used. Testing a method is easier than a class because there doesn’t need to be an instance created.

How it Felt After?

This is a question asked at the end of each round of a code retreat session and it’s good to answer here as part of my practice.

When I got the game working and it was beating me consistently, I was very happy. One thing that annoyed me was how long the first few moves took, but that can be solved with faster processors, right!? :-D

I loved that I took a more functional approach. Debugging recursive algorithms are tricky unless state is externalized.

Feedback

I went back told my friend about my experience and he took at a look at my code. He gave me some feedback:

  • It’s great you have tests, but it is really SLOW… (like 5min for full suite run!)
  • Implementation doesn’t look very ‘ruby’ like… there are very few classes
  • What if we wanted more player options, like choosing different display characters? play levels between computer players?

He was right on all those points. I was so singular in my focus of achieving unbeatable tic-tac-toe that I didn’t have good coding practice.

Second Attempt

With my friend’s feedback, I took another shot, I rethought my approach. The first time, I was so focused on getting ‘unbeatable play’ that I gave up almost everything else. Even with tests guiding me, I directed all the tests to support ‘unbeatable play’.

This attempt, I took what I had and started to separate or group things together into more logical components. I asked: what ‘object’ would this part of the code represent in the real world?

By using real world objects as a guide to my code objects, I made my second attempt.

Classes

These are the classes I had in my second attempt at unbeatable tic-tac-toe:

  • Board
  • Brain
  • Player
  • PlayerConfiguration
  • Present

Functions

Board

  • Class Methods
    • blank
    • create
    • opponent
  • Instance Methods
    • available_positions
    • available?
    • set
    • next_player
    • winner?
    • loser?
    • tie?
    • game_is_over
  • private
    • any_winners?

Brain

  • Class Methods
    • score_move
    • best_move
    • select_move_for_difficulty
    • difficulty_menu
    • difficulty
    • precalc_move
  • private class methods
    • config_difficulty

Player

  • Class Methods
    • remove_all
    • x
    • o
    • bring
    • create_players
  • Instance Methods
    • destroy
    • human?
    • computer?
    • get_input

PlayerConfiguration

  • Class Methods
    • start
    • configure_type_menu
    • configure_type
    • display_menu
    • display_config
    • config_display_options
  • Private Class Methods
    • player_types
    • configure_difficulty

Present

  • Class Methods
    • board
    • game_over
    • available_moves
    • get_input
    • item

Design Goals

For the second attempt, I wanted to keep as much of the first attempt that was usable while making as many ‘objects’ as possible. Each object created has very specific goals in mind.

I made the full spec suite run faster by pre-calculating the first move and that put full suite run time at just around 7 seconds! I dreaded running full suite specs in the first attempt, now running full suite is a dream and would run continuously, even implementing guard into my workflow.

Other items in the feedback I addressed in the implementation by having more classes that relate to real objects as much as possible.

Board

Board does everything a real board does: holds game state, tells which player goes next. For this, I was thinking what would a tic-tac-toe board from a toy maker would look like?

Something that would come from a toy company that makes memory. A board with as few electronics as possible to keep game state, know when the game is won or over, tells the next player to go.

Brain

While the Board is based on a toy, the Brain is what does the thinking for computer play. If the tic-tac-toe toy supported computer play, the brain would be a part of the board.

Initially, I made board have one brain, even for pure computer play. As I wrote this article, it dawned on me: each player has their own brain. So, if two computer players are playing each other, they would have their own brains, right?

Player

Player class holds game info for the player. A player’s role in the game (if they are X or O), display character (what to show on the board when they played), etc.

The class knows which of the two players has which role and the board can ask the player class for the next turn. Let the Board class ask for player input, like a toy does.

If the players are computer players, it also holds the player’s difficulty level when playing. So, a hard computer player can play an intermediate computer player. This would be a great way to ensure a simple computer player will be beaten by an intermediate computer player, and a hard player beats them all.

Player Configuration

There are a lot of configuration details involved with the player class! When rubocop complained the player class was too large, that was a good indicator to split something to another class.

Configuration seemed like a good place to split out into its own. Since player configuration is done in a game, there will be no need to redo it.

At the same time, there might be a future requirement for continuous play where players need to be reconfigured. Well, at least all the player configuration is done in a single class and separated from the game.

Here, I follow Sandi Metz’s advice:

Code that is Transparent, Reasonable, Usable, and Exemplary (TRUE) not only meets today’s needs but can also be changed to meet the needs of the future.

As much as I want to code a ‘TRUE’ configuration system with infinite play options, I only have so much time. The best thing is to make it easy to change in the future, as much as I can now.

Present

The Present class was created so all the console input and output stubbing would be done in one place instead of in each class.

At the same time, the Present class does indicate what it does: Present stuff!

How it Felt After: Second Attempt

The second attempt felt very good. Since the first attempt I created already had the ‘core’ functionality I wanted, I just had to add to it, or work around it.

Adding new specs and code was a breeze. Since that usually did not break anything except the new stuff. Moving stuff from an old class to a new class (i.e. methods from Player to PlayerConfiguration) was annoying since that would break specs in both classes.

The feeling of going from zero failing specs to specs failing in two classes just sucks. It really made me question: am I doing the right thing?

Thankfully, I had larger integration tests in the spec suite, so as long as those passed, I felt confident I was going in the right direction.

Honestly, the trickiest part was not around the ‘core’ logic at all, but around interfacing with ‘humans’. Getting input, what to do when there were errors, etc. There are many decisions to be made around anything with a human, it was a bit frustrating.

Lessons Learned

From this practice, I really learned that design needs to be practiced. Getting out of my usual routine and coding without the usual framework (cough Rails cough) really made me think and decide about parts of code I normally don’t.

At the same time, this exercise made me appreciate Rails more because I see how much work Rails is doing, not just code wise, but in forcing certain design thinking from its users and allowing enough flexibility to handle many situations.

Overall: Coding & Writing Parallels

When I compare programming to writing, my first attempt felt like it was me just ‘jamming out’ an article. Just diving in and writing.

My second attempt was more deliberate and thoughtful, where consideration was given not to just get things done, but finding a good way to structure the program in understandable ways to the reader.

The best thing that happened was getting feedback. Sometimes one is so focused on the goal, that they are blind to everything else. I was in this case. Just like a good editor would be for a writer: keep an eye on the bigger picture and details.

For me, I enjoyed this process a lot and will take on more challenges to flex my design muscle.