# == Synopsis # # Library to support a Trap the Cap player that uses potentials to select the # best move # # == Author # Neil Smith # # == Change history # Version 1.1:: 23 April 2008 require 'libttc' require 'libgenetics' # Play Trap the Cap by using potential fields. For each possible move, # calculate the field strength and pick the move with the lowest potential class Potential_player attr_reader :friend_pull, :enemy_pull, :base_pull, :safe_bonus, :capture_bonus def initialize(args, verbose = false) @friend_pull = args[:friend_pull] || 1 @enemy_pull = args[:enemy_pull] || 0.5 @base_pull = args[:base_pull] || 2 @safe_bonus = args[:safe_bonus] || 8 @capture_bonus = args[:capture_bonus] || 10 @verbose = verbose end # Find the best move of the possible ones def best_move(game, die_result) me = game.current_player possible_moves = game.possible_moves(die_result, me) scored_moves = possible_moves.collect do |m| begin game.apply_move! m score = score_position(game, me) # game.undo_move! rescue GameWonNotice score = 10000 ensure game.undo_move! end puts "#{m} scores #{score}" if @verbose [m, score] end best_move = (scored_moves.max {|a, b| a[1] <=> b[1]})[0] end # Calculate the potential score of a position for a given player def score_position(game, player) score = 0 game.pieces.each_value do |piece| here = piece.position if piece.colour == player game.pieces.each_value do |other_piece| if other_piece.colour == player score += game.board.distance_between[here.place][other_piece.position.place] * @friend_pull else score += game.board.distance_between[here.place][other_piece.position.place] * @enemy_pull end end score += piece.contains.length * @capture_bonus score += game.board.distance_between[here.place][game.board.positions[player].place] * piece.contains.length * @base_pull elsif here == game.board.positions[player] score += @safe_bonus end end score end # Convert a player to a bitstring def to_bitstring (@friend_pull * 4).to_i.to_bitstring(4).gray_encode + (@enemy_pull * 4).to_i.to_bitstring(4).gray_encode + (@base_pull * 4).to_i.to_bitstring(4).gray_encode + @safe_bonus.to_bitstring(4).gray_encode + @capture_bonus.to_bitstring(4).gray_encode end # Convert a player to a genome def to_genome Genome.new(self.to_bitstring) end end class Genome # Create a potential player from a genome def to_potential_player friend_pull = @genome[0, 4].gray_decode.to_decimal.to_f / 4 enemy_pull = @genome[4, 4].gray_decode.to_decimal.to_f / 4 base_pull = @genome[8, 4].gray_decode.to_decimal.to_f / 4 safe_bonus = @genome[12, 4].gray_decode.to_decimal capture_bonus = @genome[16, 4].gray_decode.to_decimal Potential_player.new({:friend_pull => friend_pull, :enemy_pull => enemy_pull, :base_pull => base_pull, :safe_bonus => safe_bonus, :capture_bonus => capture_bonus}) end end