On Dec 13, 2004, at 8:27 AM, Thomas Leitner wrote:

> | This week's Ruby Quiz is to implement an AI for playing Tic-Tac-Toe,
> | with a catch:  You're not allowed to embed any knowledge of the game
> | into your creation beyond how to make legal moves and recognizing 
> that
> | it has won or lost.
> |
> | Your program is expected to "learn" from the games it plays, until it
> | masters the game and can play flawlessly.
>
> So, I have also tried to program a learning AI player. However, it 
> still
> does not do what it should and I do not know why. Maybe someone with
> more brains can help???

I'm not able to see an obvious flaw in your logic, but I did want to 
compliment you on your interface.  When I saw it I though, "How did 
Thomas know to write to my Tic-Tac-Toe library?"  I'll post it below so 
you can see how similar it is.  :D

James Edward Gray II

#!/usr/bin/env ruby

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
		
		def self.name_to_index( name )
			x = name.gsub!(/([a-cA-C])/, "").to_i - 1
			y = ($1.downcase)[0] - ?a
			x + y * 3
		end
		
		def self.index_to_name( index )
			if index >= 6
				"c" + (index - 5).to_s
			elsif index >= 3
				"b" + (index - 2).to_s
			else
				"a" + (index + 1).to_s
			end
		end
		
		def initialize( squares )
			@squares = squares
		end
			
		include SquaresContainer
		
		def []( *indices )
			if indices.size == 2
				super indices[0] + indices[1] * 3
			elsif indices[0].is_a? Fixnum
				super indices[0]
			else
				super Board.name_to_index(indices[0].to_s)
			end
		end
		
		def each_row
			rows = [ [0, 1, 2], [3, 4, 5], [6, 7, 8],
					 [0, 3, 6], [1, 4, 7], [2, 5, 8],
					 [0, 4, 8], [2, 4, 6] ]
			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
		end
		
		def won?
			each_row do |row|
				return "X" if row.xs == 3
				return "O" if row.os == 3
			end
			return " " if blanks == 0
			
			false
		end
		
		def to_s
			@squares.join
		end
	end
	
	class Player
		def initialize( pieces )
			@pieces = pieces
		end
		
		attr_reader :pieces
		
		def move( board )
			raise NotImplementedError, "Player subclasses must define move()."
		end
		
		def finish( final_board )  end
	end
	
	class HumanPlayer < Player
		def move( board )
			draw_board board
			
			moves = board.moves
			print "Your move?  (format: b3)  "
			move = gets
			move.chomp!
			until moves.include?(move.downcase)
				print "Invalid move.  Try again.  "
				move = gets
				move.chomp!
			end
			move
		end
		
		def finish( final_board )
			draw_board final_board
			
			if final_board.won? == @pieces
				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
		
		private
		
		def draw_board( board )
			rows = [ [0, 1, 2], [3, 4, 5], [6, 7, 8] ]
			names = %w{a b c}
			puts
			print(rows.map do |r|
				names.shift + "  " + r.map { |e| board[e] }.join(" | ") + "\n"
			end.join("  ---+---+---\n"))
			print "   1   2   3\n\n"
		end
	end
	
	class DumbPlayer < Player
		def move( board )
			moves = board.moves
			moves[rand(moves.size)]
		end
	end
	
	class SmartPlayer < Player
		def move( board )
			moves = board.moves
			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

			if board[0] != @pieces and board[0] != " " and board[8] == " "
				return "c3"
			elsif board[8] != @pieces and board[8] != " " and board[0] == " "
				return "a1"
			elsif board[2] != @pieces and board[2] != " " and board[6] == " "
				return "c1"
			elsif board[6] != @pieces and board[6] != " " and board[2] == " "
				return "a3"
			end

			return "b2" if moves.include? "b2"
			
			return "a1" if moves.include? "a1"
			return "a3" if moves.include? "a3"
			return "c1" if moves.include? "c1"
			return "c3" if moves.include? "c3"
			
			moves[rand(moves.size)]
		end
	end
	
	class Game
		def initialize( player1, player2 )
			if rand(2) == 1
				@x_player = player1.new("X")
				@o_player = player2.new("O")
			else
				@x_player = player2.new("X")
				@o_player = player1.new("O")
			end
			
			@board = Board.new([" "] * 9)
		end
		
		attr_reader :x_player, :o_player
		
		def play
			until @board.won?
				update_board @x_player.move(@board), @x_player.pieces
				break if @board.won?
				
				update_board @o_player.move(@board), @o_player.pieces
			end
			
			if @o_player.is_a? HumanPlayer
				@o_player.finish @board
				@x_player.finish @board
			else
				@x_player.finish @board
				@o_player.finish @board
			end
		end
		
		private
		
		def update_board( move, piece )
			m = Board.name_to_index(move)
			@board = Board.new((0..8).map { |i| i == m ? piece : @board[i] })
		end
	end
end

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