A*探索アルゴリズムにチャレンジ

最近なんだかtwitterとかでよく見るので、そういえば作ったことないしひとつチャレンジしてみようかと。
A*探索アルゴリズムはたとえばシミュレーションゲームの敵の移動なんかで使われている(らしい)移動経路探索のアルゴリズム
ヒトマスずつ調べて、元からの距離とゴールまでの距離でスコアをつけ、低いマスを優先的に辿っていくことで最短距離を求める。

# A*アルゴリズムの実装にチャレンジ

G = S = 0
map = [
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,0,G,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,1],
[1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1],
[1,0,S,0,0,1,0,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,1,0,0,0,0,1,1,1,1,1,1],
[1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,1,0,0,0,0,0,1,0,0,0,1],
[1,0,0,0,0,1,1,1,1,0,0,1,0,0,0,1],
[1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1],
[1,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1],
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
]

# タイルクラス
class Tile
  attr_accessor :h, :c, :s # hは目的地までの距離、cはそのタイルまでの距離、sはhとcの合計
  attr_accessor :flag      # :openでオープン(調査中)状態、:closeでクローズ(調査済)状態
  attr_reader :x, :y       # タイルの位置
  attr_accessor :prev_tile # 一つ前のタイル

  def initialize(x, y)
    @h = 0
    @s = 0
    @c = 0
    @flag = nil
    @x = x
    @y = y
    @prev_tile = nil
  end
end

# 全部のタイル
tile_list = Array.new(16) { |y| Array.new(16) { |x| Tile.new(x, y) }}

# スタートとゴール
start_x = 2
start_y = 6
goal_x = 2
goal_y = 2

# オープンタイルリストにはスタートのタイルを入れておく
open_list = [tile_list[start_y][start_x]]
tile_list[start_y][start_x].flag = :open

# クローズタイルリストはからっぽ
close_list = []

# 進行方向一覧
ary = [[-1,0], [-1,-1], [0,-1], [1,-1], [1,0], [1,1], [0,1], [-1,1]]

# 2重ループを抜けるための終了フラグ
flag = false
while (flag == false) do
  i = 0
  # オープンタイルをs、hでソート。安定である必要は無いがなんとなく。
  open_list.sort_by {|v| [v.s, v.h, i += 1] }
  # 最も優先順位の高いタイルを取り出す
  base_tile = open_list.shift

  # 全進行方向のタイルを調べる。ほんとはsの低いタイルが出たら打ち切るのがよい。
  ary.each do |v|
    check_tile = tile_list[base_tile.y + v[1]][base_tile.x + v[0]]
    # 未調査のタイルのみ調査。壁は無視。
    if check_tile.flag == nil and map[base_tile.y + v[1]][base_tile.x + v[0]] != 1
      # c,h,sを算出、格納
      check_tile.c = [(check_tile.x - start_x).abs, (check_tile.y - start_y).abs].max
      check_tile.h = [(check_tile.x - goal_x).abs, (check_tile.y - goal_y).abs].max
      check_tile.s = check_tile.c + check_tile.h
      # 経路の保存
      check_tile.prev_tile = base_tile
      # オープンタイルにする
      check_tile.flag = :open
      open_list.push(check_tile)
      # ゴール
      if check_tile.x == goal_x and check_tile.y == goal_y
        flag = true
        break
      end
    end
  end
  base_tile.flag = :close
  close_list.push(base_tile)
end

# ゴールから辿る
x = goal_x
y = goal_y
loop do
  map[y][x] = "*"
  break if x == start_x and y == start_y
  x, y = tile_list[y][x].prev_tile.x, tile_list[y][x].prev_tile.y
end

# 結果表示
map.each do |m|
  m.each do |i|
    print i
    print ","
  end
  print "\n"
end

結果。

1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
1,0,*,0,0,0,0,0,0,0,0,0,0,0,0,1,
1,0,0,*,*,*,*,*,*,*,*,*,0,0,0,1,
1,1,1,1,1,1,1,1,1,1,1,1,*,0,0,1,
1,0,0,0,0,1,0,0,0,0,0,*,0,0,0,1,
1,0,*,0,0,1,0,0,0,0,*,0,0,0,0,1,
1,0,0,*,0,1,0,0,0,*,0,0,0,0,0,1,
1,0,0,0,*,1,0,0,*,0,1,1,1,1,1,1,
1,0,0,0,*,1,0,*,0,0,0,0,0,0,0,1,
1,0,0,0,*,1,0,*,0,0,0,0,0,0,0,1,
1,0,0,0,*,1,0,0,*,0,0,1,0,0,0,1,
1,0,0,0,*,1,1,1,1,*,0,1,0,0,0,1,
1,0,0,0,0,*,*,*,1,*,0,1,0,0,0,1,
1,0,0,0,0,0,0,0,*,0,0,1,0,0,0,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,

ちなみに所要時間は1時間ほど。
動作の正しさは検証していないので参考にする人がいたら注意。