Código Completo del TicTacToe

#!/usr/bin/env ruby
#---
# Visit http://www.pragmaticprogrammer.com/titles/fr_quiz 
#---


module TicTacToe
  module SquaresContainer
    def []( index ) @squares[index] end

    def blanks()  @squares.find_all { |s| s == " " }.size end
    def os()      @squares.find_all { |s| s == "O" }.size end
    def xs()      @squares.find_all { |s| s == "X" }.size end
  end
  
  class Board
    class Row
      def initialize( squares, names )
        @squares  = squares
        @names    = names
      end
      
      include SquaresContainer
      
      def to_board_name( index ) 
        Board.index_to_name(@names[index]) 
      end
    end
    
    MOVES = %w{a1    a2   a3   b1   b2   b3   c1   c2   c3}
    # Define constant INDICES
    INDICES = Hash.new { |h, k| h[k] = MOVES.find_index(k) }

    def self.name_to_index( name )# Receives "b2" and returns 4
      INDICES[name]
    end
    
    def self.index_to_name( index ) # Receives the index, like 4 and returns "b2"
      MOVES[index]
    end
    
    def initialize( squares )
      @squares = squares # An array of Strings: [ " ", " ", " ", " ", "X", " ", " ", " ", "O"]
    end
      
    include SquaresContainer
    
    def []( *indices )
      if indices.size == 2                  # board[1,2] is @squares[7]
        super indices[0] + indices[1] * 3   # calls SquaresContainer [] method
      elsif indices[0].is_a? Fixnum         # board[7]
        super indices[0]
      else                                  # board["b2"]
        super Board.name_to_index(indices[0].to_s)
      end
    end

    def []=(indice, value)                  # board["b2"] = "X"
      m = Board.name_to_index(indice)
      @squares[m] = value
    end
    
    HORIZONTALS = [ [0, 1, 2], [3, 4, 5], [6, 7, 8] ]
    COLUMNS     = [ [0, 3, 6], [1, 4, 7], [2, 5, 8] ]
    DIAGONALS   = [ [0, 4, 8], [2, 4, 6] ]
    ROWS = HORIZONTALS + COLUMNS + DIAGONALS

    def each_row
      ROWS.each do |e|
        yield Row.new(@squares.values_at(*e), e)
      end
    end
    
    def moves
      moves = [ ]
      @squares.each_with_index do |s, i|
        moves << Board.index_to_name(i) if s == " "
      end
      moves # returns the set of feasible moves [ "b3", "c2", ... ]
    end
    
    def won?
      each_row do |row|
        return "X" if row.xs == 3 # "X" wins
        return "O" if row.os == 3 # "O" wins
      end
      return " " if blanks == 0   # tie
      false
    end
    
    BOARD =<<EOS

  +---+---+---+
a | 0 | 1 | 2 |
  +---+---+---+
b | 3 | 4 | 5 |
  +---+---+---+
c | 6 | 7 | 8 |
  +---+---+---+
    1   2   3

EOS
    def to_s
      BOARD.gsub(/(\d)(?= \|)/) { |i| @squares[i.to_i] }
    end

  end
end

module TicTacToe
  class Player
    def initialize( mark )
      @mark = mark # "X" or "O" or " "
    end
    
    attr_reader :mark
    
    def move( board )
      raise NotImplementedError, "Player subclasses must define move()."
    end
    
    def finish( final_board )  
    end
  end
end

module TicTacToe
  class HumanPlayer < Player
    def move( board )
      print board
      
      moves = board.moves
      print "Your move?  (format: b3)  "
      move = $stdin.gets
      until moves.include?(move.chomp.downcase)
        print "Invalid move.  Try again.  "
        move = $stdin.gets
      end
      move.chomp
    end
    
    def finish( final_board )
      print final_board
      
      if final_board.won? == @mark
        print "Congratulations, you win.\n\n"
      elsif final_board.won? == " "
        print "Tie game.\n\n"
      else
        print "You lost tic-tac-toe?!\n\n"
      end
    end
    
  end
end

module TicTacToe
  class DumbPlayer < Player
    def move( board )
      moves = board.moves
      moves[rand(moves.size)]
    end
  end
  
  class SmartPlayer < Player
    def move( board )
      moves = board.moves
      
      # If I have a win, take it.  If he is threatening to win, stop it.
      board.each_row do |row|
        if row.blanks == 1 and (row.xs == 2 or row.os == 2)
          (0..2).each do |e|
            return row.to_board_name(e) if row[e] == " "
          end
        end
      end

      # Take the center if open.
      return "b2" if moves.include? "b2"

      # Defend opposite corners.
      if board[0] != @mark and board[0] != " " and board[8] == " "
        return "c3"
      elsif board[8] != @mark and board[8] != " " and board[0] == " "
        return "a1"
      elsif board[2] != @mark and board[2] != " " and board[6] == " "
        return "c1"
      elsif board[6] != @mark and board[6] != " " and board[2] == " "
        return "a3"
      end
      
      # Defend against the special case XOX on a diagonal.
      if board.xs == 2 and board.os == 1 and board[4] == "O" and
         (board[0] == "X" and board[8] == "X") or
         (board[2] == "X" and board[6] == "X")
        return %w{a2 b1 b3 c2}[rand(4)]
      end
      
      # Or make a random move.
      moves[rand(moves.size)]
    end
  end
end

module TicTacToe
  class Game
    def initialize( player1, player2, random = true )
      if random and rand(2) == 1
        @x_player = player2.new("X")
        @o_player = player1.new("O")
      else
        @x_player = player1.new("X")
        @o_player = player2.new("O")
      end
      
      @board = Board.new([" "] * 9)
    end
    
    attr_reader :x_player, :o_player
    
    def play
      until @board.won?
        @board[@x_player.move(@board)] = @x_player.mark
        break if @board.won?
        
        @board[@o_player.move(@board)] = @o_player.mark
      end
      
      @o_player.finish @board
      @x_player.finish @board
    end
    
  end
end

if __FILE__ == $0
  if ARGV.size > 0 and ARGV[0] == "-d"
    game = TicTacToe::Game.new TicTacToe::HumanPlayer,
                   TicTacToe::DumbPlayer
  else
    game = TicTacToe::Game.new TicTacToe::HumanPlayer,
                   TicTacToe::SmartPlayer
  end
  game.play
end



Casiano Rodriguez León 2015-01-07