class TictactoeJ8th::AI

Attributes

token[R]

Public Class Methods

new(token) click to toggle source
# File lib/tictactoe_j8th/ai.rb, line 6
def initialize(token)
  @token = token
end

Public Instance Methods

move(board) click to toggle source
# File lib/tictactoe_j8th/ai.rb, line 10
def move(board)
  return nil if board.full?

  # ####### Speed Hacks #######
  # Hacks to speed up minimax by bypassing most of the possible games.
  # The return statements here are important.  We make a move, then
  # bail out so that we don't end up analyzing all the possible games further down.
  # TODO:  See if there is a craftier, less hackish way to speed things up.

  # If the board is empty, always take the middle.
  if board.empty?
    board.place(token, 0)
    return 0
  end

  open_spots = get_open_spots(board)
  # If there is one spot taken, then always take the middle.
  # Unless the middle is taken already, then take the corner.
  if open_spots.count == Board::BOARD_SIZE-1
    spot = board[4].nil? ? 4 : 0
    board.place(token, spot)
    return spot
  end
  # ###### End Hacks ########


  enemy_token = discover_enemy_token(board)
  # Better here, but we do it above for our speed hacks.
  #open_spots = get_open_spots(board)
  games = Hash.new

  open_spots.each do |i|
    board_copy = board.create_copy
    board_copy.place(token, i)
    game = Game.new(board_copy, AI.new(enemy_token), AI.new(token))
    if game.winner == token
      board.place(token, i)
      return i
    end
    games[i] = game
  end

  scores = Hash.new

  games.each do |i, game|
    game.play
    scores[i] = 1 if game.winner == token
    scores[i] = -1 if game.winner == enemy_token
    scores[i] = 0 if game.winner.nil? and game.game_over?
  end

  max = scores.max_by { |k, v| v }
  spot = max[0]
  board.place(token, spot)
  spot
end

Private Instance Methods

discover_enemy_token(board) click to toggle source

TODO: Messy, reconsider this later. Determine the enemy token. Anything we find on the board that is not our token is considered the 'enemy_token' We assume there is only one enemy token. If we do not find any enemy tokens on the board, we assume the enemy token to be 'O',

or we assume it to be 'X' if our own token is 'O'.
# File lib/tictactoe_j8th/ai.rb, line 76
def discover_enemy_token(board)
  enemy_token = nil
  (0..Board::BOARD_SIZE-1).each do |i|
    enemy_token = board[i] if board[i] != token and board[i] != nil
  end
  if enemy_token.nil?
    enemy_token = token == :X ? :O : :X
  end
  enemy_token
end
get_open_spots(board) click to toggle source
# File lib/tictactoe_j8th/ai.rb, line 87
def get_open_spots(board)
  open_spots = Array.new
  (0..Board::BOARD_SIZE-1).each do |i|
    open_spots.push(i) if board[i].nil?
  end
  open_spots
end