maze_solver 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/README.md +20 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/maze_solver.rb +73 -0
- data/lib/maze_solver/element.rb +47 -0
- data/lib/maze_solver/maze.rb +128 -0
- data/lib/maze_solver/position.rb +38 -0
- data/lib/maze_solver/tree.rb +48 -0
- data/lib/maze_solver/version.rb +3 -0
- data/maze_solver.gemspec +23 -0
- metadata +100 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: dce2968fbc1b982e1bc809b3c04721d97a5b011a
|
4
|
+
data.tar.gz: bdb569186748d7521587601d64f95440794d886d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6315411ca831fdd12408374e8a515ad11653c854bba934934bf308da8a49474e1f345f7689268cac5a6db7eb1a3a0a76a8fdf51926c82422d6a1f7adda478aeb
|
7
|
+
data.tar.gz: 609cef963ec700f6aadd4bf0fb0819315b1e3edfcb405f44d7c4d38ed1134345855785d386a3750b4ab00d48d794ba86a7839d5f2a253d0b0922e902580ba31d
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# MazeSolver
|
2
|
+
|
3
|
+
This is a Maze Solver for labyrinths with specific format
|
4
|
+
_ : PATH
|
5
|
+
X : WALL
|
6
|
+
S : START
|
7
|
+
G : GOAL (EXIT)
|
8
|
+
|
9
|
+
It finds the shortest Path using A* Algorithm
|
10
|
+
|
11
|
+
## Use
|
12
|
+
|
13
|
+
```Ruby
|
14
|
+
require 'maze_solver'
|
15
|
+
|
16
|
+
MazeSolver.to_image('file_name')
|
17
|
+
|
18
|
+
MazeSolver.to_json('file_name')
|
19
|
+
```
|
20
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "maze_solver"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/lib/maze_solver.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
require "maze_solver/version"
|
2
|
+
require "maze_solver/position"
|
3
|
+
require "maze_solver/element"
|
4
|
+
require "maze_solver/maze"
|
5
|
+
require "json"
|
6
|
+
|
7
|
+
module MazeSolver
|
8
|
+
|
9
|
+
## Better to implemented on a class after thoughts......Anyway..mabe later!!!
|
10
|
+
|
11
|
+
def to_json(file_name)
|
12
|
+
|
13
|
+
maze_array = from_file(file_name)
|
14
|
+
maze_list = to_maze(maze_array)
|
15
|
+
maze = Maze.new(maze_list)
|
16
|
+
solution = maze.solve
|
17
|
+
|
18
|
+
solution_array = solution.map do |element|
|
19
|
+
{ value: element.value, position: "(#{element.position.x}, #{element.position.y})" }
|
20
|
+
end
|
21
|
+
|
22
|
+
JSON.generate(solution_array)
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_image(file_name)
|
26
|
+
## Print solution to console
|
27
|
+
maze_array = from_file(file_name)
|
28
|
+
maze_list = to_maze(maze_array)
|
29
|
+
maze = Maze.new(maze_list)
|
30
|
+
solution = maze.solve
|
31
|
+
|
32
|
+
maze_list.each do |element|
|
33
|
+
maze_array[element.position.y][element.position.x] = '*' if solution.find { |se| se == element }
|
34
|
+
end
|
35
|
+
|
36
|
+
puts '!!!!!!!SOLUTIOM!!!!!!!'
|
37
|
+
print maze_array.join('')
|
38
|
+
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def from_file(file_name)
|
43
|
+
## Read file and export it to a 2D array
|
44
|
+
## Throw error if file doesnt exist
|
45
|
+
|
46
|
+
begin
|
47
|
+
File.readlines(file_name).map { |line| line.split('') }
|
48
|
+
rescue abort("can't open #{file_name}" )
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_maze(maze_array)
|
53
|
+
maze = []
|
54
|
+
maze_array.each_with_index do |line, line_index|
|
55
|
+
line.each_with_index do |char, col_index|
|
56
|
+
case char
|
57
|
+
when '_'
|
58
|
+
then maze << Element.new(char, Position.new(col_index, line_index,), 'PATH')
|
59
|
+
when 'X'
|
60
|
+
then maze << Element.new(char, Position.new(col_index, line_index,), 'WALL')
|
61
|
+
when 'S'
|
62
|
+
then maze << Element.new(char, Position.new(col_index, line_index,), 'START')
|
63
|
+
when 'G'
|
64
|
+
then maze << Element.new(char, Position.new(col_index, line_index,), 'GOAL' )
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
maze
|
69
|
+
end
|
70
|
+
|
71
|
+
module_function :to_json, :to_image, :from_file, :to_maze
|
72
|
+
end
|
73
|
+
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module MazeSolver
|
2
|
+
class Element
|
3
|
+
## Represent an element in the Maze, defined by his position and value
|
4
|
+
## There are also some Astar factors like g, h, f functions
|
5
|
+
## Also the precedent element, (parent)
|
6
|
+
## visited is a flag that aknowledge that the element had been visited
|
7
|
+
|
8
|
+
attr_accessor :g, :h, :f, :parent
|
9
|
+
attr_reader :value, :position, :type
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
## Represent the class
|
13
|
+
"(#{@position}), #{@value}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def ==(element)
|
17
|
+
## Compare two elements by its position
|
18
|
+
return true if ( @position == element.position && @value == element.value && @type == element.type )
|
19
|
+
return false
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(value, position, type)
|
23
|
+
@value = value
|
24
|
+
@position = position
|
25
|
+
@type = type
|
26
|
+
|
27
|
+
## Default Astar values
|
28
|
+
@g = 0
|
29
|
+
@h = 0
|
30
|
+
@f = 0
|
31
|
+
@parent = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
def is_goal?
|
35
|
+
## is the end point?
|
36
|
+
return true if @type == 'GOAL'
|
37
|
+
false
|
38
|
+
end
|
39
|
+
|
40
|
+
def is_path?
|
41
|
+
## Can be traversed?
|
42
|
+
return true if ( @type == 'PATH' || @type == 'START' || @type == 'GOAL' )
|
43
|
+
false
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require "maze_solver/tree"
|
2
|
+
|
3
|
+
module MazeSolver
|
4
|
+
class Maze
|
5
|
+
## Class which Represent the maze
|
6
|
+
|
7
|
+
attr_reader :maze
|
8
|
+
|
9
|
+
def initialize(maze)
|
10
|
+
@maze = maze
|
11
|
+
@closed = [] # Already proccessed
|
12
|
+
|
13
|
+
set_start_and_goal
|
14
|
+
end
|
15
|
+
|
16
|
+
def solve
|
17
|
+
## Begin process !!
|
18
|
+
tree = Tree.new # Instanciate a tree with start point as first pushing
|
19
|
+
tree.push(@start, @start.f)
|
20
|
+
|
21
|
+
reverse_path( algorithm(tree) ) # RUUUN
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def algorithm(tree)
|
27
|
+
## Core of this gem
|
28
|
+
## Recurcive implementetion
|
29
|
+
|
30
|
+
element = tree.next
|
31
|
+
@closed.push(element)
|
32
|
+
|
33
|
+
return element if element.is_goal? ## Found EXIT!!!!
|
34
|
+
|
35
|
+
neighbors(element).each do |neighbor|
|
36
|
+
if !in_closed?(neighbor)
|
37
|
+
|
38
|
+
if tree.in?(neighbor, neighbor.f)
|
39
|
+
if neighbor.g > element.g + 1
|
40
|
+
update_neighbor(neighbor, element)
|
41
|
+
end
|
42
|
+
else
|
43
|
+
|
44
|
+
update_neighbor(neighbor, element)
|
45
|
+
tree.push(neighbor, neighbor.f)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
algorithm(tree)
|
51
|
+
end
|
52
|
+
|
53
|
+
def set_start_and_goal
|
54
|
+
## Check and set if there is start and goal point
|
55
|
+
start = []
|
56
|
+
goal = []
|
57
|
+
|
58
|
+
@maze.each do |elem|
|
59
|
+
start << elem if elem.type == 'START'
|
60
|
+
goal << elem if elem.type == 'GOAL'
|
61
|
+
end
|
62
|
+
|
63
|
+
abort('Check if there is exactly
|
64
|
+
one start and one end point') if (start.length != 1 || goal.length != 1 )
|
65
|
+
|
66
|
+
@start = start.pop
|
67
|
+
@goal = goal.pop
|
68
|
+
end
|
69
|
+
|
70
|
+
def reverse_path(element, acc=[])
|
71
|
+
## Recurcively find path
|
72
|
+
return acc << element if element == @start
|
73
|
+
|
74
|
+
acc << element
|
75
|
+
reverse_path(element.parent, acc)
|
76
|
+
end
|
77
|
+
|
78
|
+
def in_closed?(element)
|
79
|
+
## Is the element proccessed?
|
80
|
+
@closed.find { |ce| ce == element }
|
81
|
+
end
|
82
|
+
|
83
|
+
def h(element)
|
84
|
+
## heuristic function (Guessing distance to goal)
|
85
|
+
## I implement it this way can be implement it with another way
|
86
|
+
( @goal.position.x - element.position.x ).abs +
|
87
|
+
( @goal.position.y - element.position.y ).abs
|
88
|
+
end
|
89
|
+
|
90
|
+
def valid_neighbor(position)
|
91
|
+
## Check if neighbor is a valid path (_, G, S )
|
92
|
+
neighbor = @maze.find { |elem| elem.position == position }
|
93
|
+
|
94
|
+
if (neighbor)
|
95
|
+
return neighbor if neighbor.is_path?
|
96
|
+
end
|
97
|
+
nil
|
98
|
+
end
|
99
|
+
|
100
|
+
def neighbors(element)
|
101
|
+
## Adjacent Elements !!Only valid ones!!
|
102
|
+
neighbors = []
|
103
|
+
|
104
|
+
directions = [
|
105
|
+
element.position.north,
|
106
|
+
element.position.east,
|
107
|
+
element.position.south,
|
108
|
+
element.position.west
|
109
|
+
]
|
110
|
+
|
111
|
+
directions.each do |d|
|
112
|
+
neighbor = valid_neighbor(d)
|
113
|
+
neighbors << neighbor if neighbor
|
114
|
+
end
|
115
|
+
neighbors
|
116
|
+
end
|
117
|
+
|
118
|
+
def update_neighbor(neighbor, element)
|
119
|
+
## process neighbor
|
120
|
+
|
121
|
+
neighbor.g = element.g + 1
|
122
|
+
neighbor.h = h(neighbor)
|
123
|
+
neighbor.f = neighbor.g + neighbor.h
|
124
|
+
neighbor.parent = element
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module MazeSolver
|
2
|
+
class Position
|
3
|
+
## Position Of Element in the maze (Cartesians)##
|
4
|
+
attr_reader :x, :y # column, row position respectively
|
5
|
+
|
6
|
+
def initialize(x, y)
|
7
|
+
@x = x
|
8
|
+
@y = y
|
9
|
+
end
|
10
|
+
|
11
|
+
def ==(position)
|
12
|
+
## Compare two positions ##
|
13
|
+
@x == position.x && @y == position.y
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
## Represent a Position ##
|
18
|
+
"(#{@x}, #{@y})"
|
19
|
+
end
|
20
|
+
|
21
|
+
## Adjacent position (In a Cartesian way) ##
|
22
|
+
def north
|
23
|
+
Position.new(@x, @y-1)
|
24
|
+
end
|
25
|
+
|
26
|
+
def east
|
27
|
+
Position.new(@x+1, @y)
|
28
|
+
end
|
29
|
+
|
30
|
+
def south
|
31
|
+
Position.new(@x, @y+1)
|
32
|
+
end
|
33
|
+
|
34
|
+
def west
|
35
|
+
Position.new(@x-1, @y)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module MazeSolver
|
2
|
+
class Tree
|
3
|
+
## Binary Tree Interprentation for Apress Open List
|
4
|
+
def initialize
|
5
|
+
## Tree in hash form with priority as key and array
|
6
|
+
## of elements as value
|
7
|
+
@tree = Hash.new { |hash, key| hash[key] = [] }
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
## Representation of Tree
|
12
|
+
o = '{'
|
13
|
+
@tree.each do |k, v|
|
14
|
+
o += ":#{k} => [\n"
|
15
|
+
v.each do |el|
|
16
|
+
o += "#{el} \n"
|
17
|
+
end
|
18
|
+
o+="\n]\n"
|
19
|
+
end
|
20
|
+
o += "}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def push(element, priority)
|
24
|
+
## push an element in the stack
|
25
|
+
@tree[priority] << element
|
26
|
+
end
|
27
|
+
|
28
|
+
def in?(element, priority)
|
29
|
+
## check if elements is in the stack
|
30
|
+
if @tree.has_key?(priority)
|
31
|
+
return true if @tree[priority].find { |elem| elem == element }
|
32
|
+
end
|
33
|
+
false
|
34
|
+
end
|
35
|
+
|
36
|
+
def next
|
37
|
+
## Poping an element
|
38
|
+
|
39
|
+
return nil if @tree.empty?
|
40
|
+
|
41
|
+
priority, element = *@tree.min # get the min priority
|
42
|
+
result = element.shift # get the first element
|
43
|
+
@tree.delete(priority) if element.empty? # if branch with this priority is empty delete it
|
44
|
+
|
45
|
+
return result
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/maze_solver.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'maze_solver/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "maze_solver"
|
8
|
+
spec.version = MazeSolver::VERSION
|
9
|
+
spec.authors = ["Ioannis-Andreas Philippas"]
|
10
|
+
spec.email = ["ioannis.philippas@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = 'Solve maze with astar and simple algorithm'
|
13
|
+
spec.description = 'Solve maze with astar and simple algorithm'
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
16
|
+
spec.bindir = "exe"
|
17
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_development_dependency "bundler", "~> 1.12"
|
21
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
22
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: maze_solver
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ioannis-Andreas Philippas
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-10-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.12'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.12'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
description: Solve maze with astar and simple algorithm
|
56
|
+
email:
|
57
|
+
- ioannis.philippas@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- ".rspec"
|
64
|
+
- ".travis.yml"
|
65
|
+
- Gemfile
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- bin/console
|
69
|
+
- bin/setup
|
70
|
+
- lib/maze_solver.rb
|
71
|
+
- lib/maze_solver/element.rb
|
72
|
+
- lib/maze_solver/maze.rb
|
73
|
+
- lib/maze_solver/position.rb
|
74
|
+
- lib/maze_solver/tree.rb
|
75
|
+
- lib/maze_solver/version.rb
|
76
|
+
- maze_solver.gemspec
|
77
|
+
homepage:
|
78
|
+
licenses: []
|
79
|
+
metadata: {}
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options: []
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
requirements: []
|
95
|
+
rubyforge_project:
|
96
|
+
rubygems_version: 2.5.1
|
97
|
+
signing_key:
|
98
|
+
specification_version: 4
|
99
|
+
summary: Solve maze with astar and simple algorithm
|
100
|
+
test_files: []
|