fork download
  1. # frozen_string_literal: true
  2.  
  3. class Maze
  4. NO_SOLUTION = 'No Solution'
  5.  
  6. attr_accessor :field, :width, :height, :pos_x, :pos_y, :way, :step, :memo
  7.  
  8. def self.solve(field)
  9. new(field).solve
  10. end
  11.  
  12. def initialize(field)
  13. @field = field.split($/).map { |e| e.split('') }
  14. @width = @field.first.size
  15. @height = @field.size
  16. @pos_x, @pos_y = end_pos(?S)
  17. @step = 0
  18. @memo = {}
  19. end
  20.  
  21. def end_pos(sym)
  22. @field.flatten.index(sym).divmod(@width).reverse
  23. end
  24.  
  25. def movable
  26. [
  27. [@pos_x - 1, @pos_y, ?L],
  28. [@pos_x + 1, @pos_y, ?R],
  29. [@pos_x, @pos_y - 1, ?U],
  30. [@pos_x, @pos_y + 1, ?D]
  31. ].each_with_object([]) do |(x, y, way), ary|
  32. ary << way if (0...@width).include?(x) &&
  33. (0...@height).include?(y) &&
  34. @field[y][x] != ?# &&
  35. @way != way
  36. end
  37. end
  38.  
  39. def move(way)
  40. case way
  41. when ?L then @pos_x -= 1
  42. when ?R then @pos_x += 1
  43. when ?U then @pos_y -= 1
  44. when ?D then @pos_y += 1
  45. end
  46. @way = way
  47. end
  48.  
  49. def set(x, y, step, way)
  50. @pos_x = x
  51. @pos_y = y
  52. @step = step
  53. @way = way
  54. end
  55.  
  56. def solve
  57. ways = movable.each_with_object([]) do |way, ary|
  58. step = passed(way)
  59. ary << way unless step && @step >= step
  60. end
  61. return NO_SOLUTION if ways.empty?
  62. return @step + 1 if ways.any? { |way| symbol_in(way) == ?G }
  63. min = ways.map { |way| test_move(way) }.reject { |e| e == NO_SOLUTION }.min
  64. min ? min : NO_SOLUTION
  65. end
  66.  
  67. def symbol_in(way)
  68. case way
  69. when ?L then @field[@pos_y][@pos_x - 1]
  70. when ?R then @field[@pos_y][@pos_x + 1]
  71. when ?U then @field[@pos_y - 1][@pos_x]
  72. when ?D then @field[@pos_y + 1][@pos_x]
  73. end
  74. end
  75.  
  76. def test_move(test_way)
  77. pos_x, pos_y, step, way = @pos_x, @pos_y, @step, @way
  78. move(test_way)
  79. @memo[[@pos_x, @pos_y, test_way]] = @step += 1
  80. _step = solve
  81. set(pos_x, pos_y, step, way)
  82. _step
  83. end
  84.  
  85. def passed(way)
  86. case way
  87. when ?L then @memo[[@pos_x - 1, @pos_y, way]]
  88. when ?R then @memo[[@pos_x + 1, @pos_y, way]]
  89. when ?U then @memo[[@pos_x, @pos_y - 1, way]]
  90. when ?D then @memo[[@pos_x, @pos_y + 1, way]]
  91. end
  92. end
  93.  
  94. end
  95.  
  96.  
  97. [<<~EOT, <<~EOT, <<~EOT, <<~EOT].each { |s| puts "%s=> %s\n\n" %[s, Maze.solve(s)] }
  98. S....
  99. #....
  100. ..#..
  101. ....G
  102. EOT
  103. S.....G
  104. .......
  105. EOT
  106. S.#G
  107. ....
  108. EOT
  109. S#G
  110. EOT
  111.  
Success #stdin #stdout 0.02s 6184KB
stdin
Standard input is empty
stdout
S....
#....
..#..
....G
=> 9

S.....G
.......
=> 12

S.#G
....
=> No Solution

S#G
=> No Solution