8
\$\begingroup\$

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 [email protected]?
 @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
200_success
145k22 gold badges190 silver badges478 bronze badges
asked Apr 30, 2015 at 4:15
\$\endgroup\$
5
  • \$\begingroup\$ Does this code live in a repository somewhere that is easy to grab and work with? \$\endgroup\$ Commented Apr 30, 2015 at 4:43
  • \$\begingroup\$ 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. \$\endgroup\$ Commented Apr 30, 2015 at 5:14
  • \$\begingroup\$ 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. \$\endgroup\$ Commented Apr 30, 2015 at 5:20
  • \$\begingroup\$ 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. \$\endgroup\$ Commented Apr 30, 2015 at 6:15
  • \$\begingroup\$ "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. \$\endgroup\$ Commented Apr 30, 2015 at 6:54

1 Answer 1

2
\$\begingroup\$

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.

answered Apr 30, 2015 at 16:29
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.