Hackifying the Tic-Tac-Toe With AI Project Tests

Posted by Thomas Gray on January 15, 2019

Ok, don’t hate me, but I used the #instance_variable_set method in my latest Flatiron lab.

Why is this so bad? For one, it’s fairly common knowledge within the Ruby community that #instance_variable_set and #instance_variable_get are hacky ways of creating reader/writer methods for instance variables. As Avi, the Dean of Flatirion, put it (and I’m paraphrasing here): “It’s syntactic vinegar. The method name itself ends with a verb, which shouldn’t feel right.” And he’s right–it feels very wrong, but as far as I’m aware I didn’t have any other choice.

The reason I had to use it was because of how the tests are written for the lab I was working on. In Tic Tac Toe with AI, I needed to build the logic of how a computer player would play tic tac toe. I wanted the computer player to be able to utilize some of the methods defined in the Game class (e.g. #won?, #over?) as well as the WIN_COMBINATIONS constant, so that the computer would know which positions to take on the board to get it closer to victory, or which positions to take to prevent a loss. This meant that I would need to either pass the instance of the Game class to the Players::Computer instance or recode all of the logic from the Game class into Players::Computer. Since it felt even more wrong to copy and paste a bunch of methods from one class to another, and since I’m lazy, I chose the former.

Ideally, I would have syntactically sugar-ified this by passing in the instance of the Game class to the computer player upon initialization of the player, however the tests wouldn’t allow for that. I would get an unexpected number of arguments error for the tests relating to the Players::Computer instance, because instead of receiving one argument per its expectation (the player’s “token”, either an “X” or an “O”), it is now receiving two, hence the failure.

To work around this, I added a few lines of code to the Game class’s #initialize method, which now looks like this:

  def initialize(player_1 = Players::Human.new("X"), player_2 = Players::Human.new("O"), board = Board.new)
    @player_1 = player_1
    @player_2 = player_2
    @board = board
    if player_1.instance_of? Players::Computer
      player_1.instance_variable_set(:@game, self)
    end
    if player_2.instance_of? Players::Computer
      player_2.instance_variable_set(:@game, self)
    end
  end

Full repo here: https://github.com/tgray017/ttt-with-ai-project-v-000.

I know, it looks awful. I’m a terrible human being for committing such a travesty. I may as well have just cut someone off in traffic while giving them the finger. I don’t know how I’ll be able to live with myself knowing that this exists in my GitHub repo for anyone to see, but I do know that I’m looking forward to writing my own tests so that I can just change the expectations of the test instead of hackifying the program to pass the test.