module TicTacToe
class Game
def self.play
new.run
end
def players
@players ||= %w(X O).cycle
end
def board
@board ||= Board.new
end
def run
play_turn until board.finished?(@current_player)
end
def play_turn
advance_turn
make_move(read_move)
end
def advance_turn
@current_player = players.next
end
def make_move(move)
board.set(move.row, move.col, move.player)
end
def read_move
loop do
board.draw
move = Move.read(@current_player)
return move if valid? move
end
end
def valid?(move)
move.valid? && board.free?(move.row, move.col)
end
end
class Move < Struct.new(:row, :col, :player);
def valid?
row && col
end
def self.read(player)
row,col = gets.split.map(&:to_i)
new(row, col, player)
end
end
class Board
FREE = ' '
def initialize
@rows ||= Array.new(3) { Array.new(3){ FREE } }
end
def set(row, col, player)
@rows[row][col] = player
end
def draw
puts "\n 0 1 2"
puts @rows.map.with_index{ |row, x| "#{x}:#{row * "|"}" } * "\n"
print "\nrow col >> "
end
def free?(row, col)
@rows.fetch(row).fetch(col) == FREE
rescue IndexError
puts 'Out of bounds'
false
end
def finished?(player)
full? || tic_tac_toe?(player)
end
def full?
@rows.flatten.none?{|c| c == FREE }
end
def tic_tac_toe?(player)
lines.any? {|line| line.all? { |cell| cell == player }}
end
def lines
rows + cols + diagonals
end
def rows
@rows
end
def cols
@rows.transpose
end
def diagonals
[
(0..2).map{|d| @rows[d][d] },
(0..2).map{|d| @rows[d][2-d] }
]
end
end
end
if __FILE__ == $PROGRAM_NAME
TicTacToe::Game.play
end