ブレゼンハムの線分アルゴリズムを用いた追跡

ゲーム開発者のためのAI入門に載ってたタイルベースのLOS追跡アルゴリズムRubyで。
まずは下準備としてタイルを表現するクラスを準備。

class Tile    
  def initialize(x, y)
    @cel = []  
    (0..y).each do |i|
      @cel[i] = Array.new(x, 0)
    end
  end
  
  def set_value(x, y, value)
    @cel[y][x] = value
  end
  
  def show
    @cel.each do |row|
      p row
    end
  end
end


次に基本的な追跡アルゴリズムの時に使用したPlayerクラスを継承し、追跡を行うAIクラスを用意。build_path_to_target()にてブレゼンハムの線分アルゴリズムを用いて追跡パスを組み立てています。

class AI < Player
  private
  def build_path_to_target(prey)
    @path = []
    delta_x = prey.x - @x
    delta_y = prey.y - @y
    
    step_x = delta_x <=> 0
    step_y = delta_y <=> 0
            
    delta_x = (delta_x * 2).abs
    delta_y = (delta_y * 2).abs
    
    next_x = @x
    next_y = @y
    @path << {"x" => next_x, "y" => next_y}
    
    if delta_x > delta_y
      fraction = delta_y - delta_x / 2
      until next_x == prey.x do
        if fraction >= 0
          next_y += step_y
          fraction -= delta_x
        end
        next_x += step_x
        fraction += delta_y
        @path << {"x" => next_x, "y" => next_y}
      end
    else
      fraction = delta_x - delta_y / 2
      until next_y == prey.y do
        if fraction >= 0
          next_x += step_x
          fraction -= delta_y
        end
        next_y += step_y
        fraction += delta_x
        @path << {"x" => next_x, "y" => next_y}
      end      
    end    
  end

  public
  def make_strategy(prey)
    @step = 0
    build_path_to_target(prey)
  end
  
  def advance
    @x = @path[@step]["x"]
    @y = @path[@step]["y"]
    @step += 1
  end  
end


メインのスクリプトはこんな感じで。X_CONST とY_CONSTでタイルの大きさを決定します。

X_CONST = Y_CONST = 15

prey = Player.new(rand(X_CONST),rand(Y_CONST), 1)
predetor = AI.new(rand(X_CONST),rand(Y_CONST), 9)

tile = Tile.new(X_CONST, Y_CONST)
tile.set_value(predetor.x, predetor.y, predetor.mark)
predetor.make_strategy(prey)
until ((predetor.x == prey.x) and (predetor.y == prey.y))
  predetor.advance
  tile.set_value(predetor.x, predetor.y, predetor.mark)
end
tile.set_value(prey.x, prey.y, prey.mark)
tile.show


実行例(タイルサイズ10*10)。9が追跡者、1がターゲットをそれぞれ表しています。

[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 9]
[0, 0, 0, 0, 0, 0, 0, 0, 9, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 9, 0]
[0, 0, 0, 0, 0, 0, 0, 9, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 9, 0, 0]
[0, 0, 0, 0, 0, 0, 9, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 9, 0, 0, 0]
[0, 0, 0, 0, 0, 9, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]