# Tic Tac Toe implementation in Ruby

Here's my first shot at implementing Tic Tac Toe in Ruby. After watching Gary Bernhardt's Functional Core, Imperative Shell, I thought this would be a great exercise in trying out the ideology presented in that screencast. Tear my code apart.

Link to GitHub repo (includes video of usage)

tic_tac_toe.rb -- the procedural shell

require_relative "board"
require_relative "cell"
require "pry"

def initialize_board
@board = Board.new
end

def choose_characters
puts "X or O?"
@player_character = gets.chomp
@computer_character = case @player_character
when "X" then "O"
when "O" then "X"
end
end

def start_game
initialize_board
choose_characters
play
end

def play
loop do
puts "Make a move: "
move = gets.chomp
board_index = Board::POSITIONS[move]
winner = @player_character

@board.board[board_index].value = @player_character

if !@board.winner?
@board.available_cells.sample.value = @computer_character
winner = @computer_character if @board.winner?
end

if @board.winner?
p "#{winner.upcase} WINS!!!"
@board.display
puts "Do you want to play again?"

start_game if gets.chomp == "Yes"
else
@board.display
play
end

break if @board.winner?
end
end

start_game


board.rb

class Board
attr_accessor :board

WINNING_COMBINATIONS = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6]
]

POSITIONS = {
"top left" => 0,
"top middle" => 1,
"top right" => 2,
"middle left" => 3,
"center" => 4,
"middle right" => 5,
"bottom left" => 6,
"bottom middle" => 7,
"bottom right" => 8
}

def initialize
@board = Array.new(9) { Cell.new }
end

def winner?
winner = false
WINNING_COMBINATIONS.each do |combination|
first_cell = board[combination[0]].value
second_cell = board[combination[1]].value
third_cell = board[combination[2]].value
consideration = [first_cell, second_cell, third_cell]

if consideration.uniq.length == 1 && first_cell.is_a?(String)
winner = true
end
end
winner
end

def available_cells
board.select { |cell| cell.value == :blank }
end

def display
rows = board.each_slice(3).to_a

rows.each do |row|
p row.map(&:value)
end
end
end


cell.rb

class Cell
attr_accessor :value

def initialize(options = {})
@value = options.fetch(:value, :blank)
end
end

• Does this code live in a repository somewhere that is easy to grab and work with? – vgoff Apr 30 '15 at 4:43
• Yes! It's here: github.com/dylanerichards/tic-tac-toe. There's even a video of me demonstrating use of the application. I'll update the original post. – Dylan Richards Apr 30 '15 at 5:14
• Now that is some very useful information to have right there. :) You even have specs... Was having some difficulties running it without referring to the code to know what the expected inputs were. – vgoff Apr 30 '15 at 5:20
• The fact that you had to explain the moves in your video by stating verbally what the list of valid locations are, means that there is not enough of a prompt, there should be something indicating move options, just like (and probably less reason to be) your prompt showing the very well know and expected "X" and "O" options for player. – vgoff Apr 30 '15 at 6:15
• "9 openings (8 indexes)" 0 is an index as well. You have 9 indices, (or the other valid English plural, 'indexes') to be sure. Otherwise you are missing a valid placement on a tic-tac-toe board. – vgoff Apr 30 '15 at 6:54

You require pry, and you don't seem to use it. Perhaps it is being used for troubleshooting, and the binding.pry or other code, has been removed. In that case it may be more appropriate to simply use the -rpry option as in ruby -rpry file.rb. Then you don't have to remember to remove that line, just the troubleshooting line. (Or even better, set up a system that lets you leave things in but turn them off and on from an option in the system call. Perhaps -w or -v flags. Perhaps an environment variable. A line in with this in mind may look like this: binding.pry if $VERBOSE And a require 'pry' if$VERBOSE)

Looking at your loop do tells me that you have an infinite loop. But you have a well defined exit strategy, there is no need for an infinite loop. Using something like until @board.winner? will run the block at least once, and will stop when your method returns true/truthy value. You also call 'play' from inside the loop inside of play, but it is already going to start from the beginning of the loop.

If you had folders bin/, lib/, I would expect some of the code in your tic-tac-toe.rb file to be in maybe bin/tic-tac-toe. The start equivalent for sure. This will allow you to use the lib/tic-tac-toe.rb file in a way that someone that requires that library would expect. Right now, you are unable to require that library file without starting the game.

The interface of using words like top left seems clumsy and error prone, even if it is nice. Creating a numbered or lettered menu or presenting the grid with numbers or letters could make play easier, less typing to do, less error prone. Perhaps a lettered 'x, y' grid. Presenting that grid would also document the game play, and make initial play more straight forward.

Your "choose_character" method does two things, it prompts and processes the choice. The @player_character and @computer_character is repetitive, confusing in a way. Simply having @player and @computer is probably clear enough. @player.character might be better, though I think we consider that letter a 'marker', as the player mark may be 'X' and the AI mark may be 'O'.

Looking at cell.rb you could be using the new named arguments in Ruby 2.0.

In the Board file you can likely use the all? Enumerable method, if an array has all? the markers that match the current players marker, then that player is the winner.