fifteen_puzzle 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in fifteen_puzzle.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Idris
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,74 @@
1
+ # FifteenPuzzle
2
+
3
+ Fifteen puzzle solver by A* algorithm.
4
+
5
+ NOTE: In puzzle with more than 20 turns program may work > 1 min.
6
+
7
+ Tested in Ruby 1.9.3-p125
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 'fifteen_puzzle'
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install fifteen_puzzle
22
+
23
+ ## Usage
24
+
25
+ 1. Create the game matrix:
26
+
27
+ @game_matrix = FifteenPuzzle::GameMatrix.new( Matrix[[ 1, 4, 14, 13],
28
+ [ 11, 9, 8, 12],
29
+ [ 3, 6, FifteenPuzzle::GameMatrix::FREE_CELL, 7],
30
+ [15, 5, 2, 10]] )
31
+
32
+ 2. Create AStar alogrithm runner:
33
+
34
+ @algorithm = FifteenPuzzle::AStarAlgorithm.new( @game_matrix )
35
+
36
+ 3. Run AStar:
37
+
38
+ @algorithm.run
39
+ #NOTE: In puzzle with more than 20 turns to solve program may work > 1 min.
40
+
41
+ 4. To access the solution:
42
+
43
+ @solution = algorithm.solution
44
+ It returns the goal state of game matrix
45
+ To Get previous states enter:
46
+
47
+ par1 = @solution.parent
48
+ next_parent = par1.parent
49
+ 5. To get the Matrix of board:
50
+
51
+ @solution.matrix
52
+ @solution.parent.matrix
53
+
54
+
55
+ ## Testing
56
+
57
+ RSpec needs for testing.
58
+ The specs can run more than 1 minute.
59
+
60
+ There are several example of matrixes.
61
+ You can change the run matrixes in spec/game_matrix_spec.rb
62
+
63
+ To run tests enter in command line:
64
+
65
+ rspec
66
+
67
+
68
+ ## Contributing
69
+
70
+ 1. Fork it
71
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
72
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
73
+ 4. Push to the branch (`git push origin my-new-feature`)
74
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/fifteen_puzzle/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+
6
+ gem.add_development_dependency "rspec"
7
+
8
+ gem.authors = ["MO-524a"]
9
+ gem.email = ["mo124a@ya.ru"]
10
+ gem.description = %q{15puzzle solver with A* algorithm}
11
+ gem.summary = %q{15puzzle solver with A* algorithm}
12
+ gem.homepage = "https://github.com/sld/astar-15puzzle-solver"
13
+
14
+ gem.files = `git ls-files`.split($\)
15
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
16
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
+ gem.name = "fifteen_puzzle"
18
+ gem.require_paths = ["lib"]
19
+ gem.version = FifteenPuzzle::VERSION
20
+ end
@@ -0,0 +1,3 @@
1
+ module FifteenPuzzle
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,246 @@
1
+ #coding: utf-8
2
+ require 'matrix'
3
+ require 'set'
4
+ require "fifteen_puzzle/version"
5
+
6
+
7
+ class Matrix
8
+ def []=(i, j, x)
9
+ @rows[i][j] = x
10
+ end
11
+ end
12
+
13
+
14
+ module FifteenPuzzle
15
+
16
+
17
+ class AStarAlgorithm
18
+ attr_reader :solution
19
+ # For Beam search
20
+ MAX_OPEN_LIST = 1000
21
+
22
+
23
+ def initialize( game_matrix )
24
+ @matrix = game_matrix
25
+ @nodes_count = 0
26
+ end
27
+
28
+
29
+ def find_cheapest( states )
30
+ min_cheapest = states.min_by{|e| e.cost}
31
+ cheapest_list = states.find_all{|e| e.cost==min_cheapest.cost}
32
+ return cheapest_list.last
33
+ end
34
+
35
+
36
+ def run
37
+ @closed_list = [].to_set
38
+ @open_list = [@matrix].to_set
39
+ while !@open_list.empty?
40
+
41
+ if @matrix.solved?
42
+ @solution = @matrix
43
+ return true
44
+ end
45
+
46
+ @closed_list << @matrix.matrix_hash
47
+ @open_list.delete(@matrix)
48
+ states = @matrix.neighbors(@open_list, @closed_list)
49
+ if !states.empty?
50
+ @open_list += states
51
+ @matrix = find_cheapest(states)
52
+ else
53
+ @matrix = @open_list.min_by{|e| e.cost}
54
+ end
55
+
56
+ # Beam search euristic
57
+ if @open_list.count > MAX_OPEN_LIST
58
+ @open_list = @open_list.sort_by{|e| e.cost}[0..MAX_OPEN_LIST/2].to_set
59
+ end
60
+ end
61
+
62
+ end
63
+
64
+
65
+ end
66
+
67
+
68
+
69
+ class GameMatrix
70
+ FREE_CELL = 0
71
+ ROW_SIZE = 4
72
+ COLUMN_SIZE = 4
73
+ CORRECT_ANSWER = Matrix[[1,2,3,4], [5,6,7,8], [9,10,11,12], [13,14,15,0]]
74
+
75
+
76
+ def initialize( matrix, parent=nil, depth_val=0 )
77
+ raise ArgumentError if matrix.row_size != ROW_SIZE || matrix.column_size != COLUMN_SIZE
78
+
79
+ @matrix = matrix
80
+ @parent = parent
81
+ @cost = 0
82
+ @depth = depth_val
83
+ end
84
+
85
+
86
+ def ==(other)
87
+ @matrix == other.matrix
88
+ end
89
+
90
+
91
+ def solved?
92
+ @matrix == CORRECT_ANSWER
93
+ end
94
+
95
+
96
+ def matrix
97
+ @matrix
98
+ end
99
+
100
+ # Get matrix_hash to save it on closed list
101
+ def matrix_hash
102
+ @matrix.hash
103
+ end
104
+
105
+
106
+ # Get current position of free cell
107
+ def free_cell
108
+ @matrix.find_index{ |elem| elem == FREE_CELL}
109
+ end
110
+
111
+
112
+ def index_exist?(i, j)
113
+ return false if i < 0 || j < 0
114
+ not @matrix[i, j].nil?
115
+ end
116
+
117
+
118
+ def parent
119
+ return @parent
120
+ end
121
+
122
+
123
+ def parent=(val)
124
+ @parent = val
125
+ end
126
+
127
+
128
+ def parents_count
129
+ count = 0
130
+
131
+ par = parent
132
+ while !par.nil?
133
+ count += 1
134
+ par = par.parent
135
+ end
136
+ return count
137
+ end
138
+
139
+
140
+ def depth
141
+ @depth
142
+ end
143
+
144
+
145
+ def depth=(val)
146
+ @depth = val
147
+ end
148
+
149
+
150
+ def cost
151
+ @cost
152
+ end
153
+
154
+
155
+ def manhattan_distance
156
+ matrix_hash = {}
157
+ @matrix.each_with_index do |e, i, j|
158
+ matrix_hash[e] = Vector[i, j]
159
+ end
160
+
161
+ etalon_hash = {}
162
+ CORRECT_ANSWER.each_with_index do |e, i, j|
163
+ etalon_hash[e] = Vector[i, j]
164
+ end
165
+
166
+ sum = 0
167
+ etalon_hash.keys.each do |e|
168
+ sum += (matrix_hash[e] - etalon_hash[e]).collect{|e| e.abs}.inject{|ind_sum,x| ind_sum + x }
169
+ end
170
+
171
+ return sum
172
+ end
173
+
174
+
175
+ def get_g
176
+ @depth
177
+ end
178
+
179
+
180
+ def get_h
181
+ manhattan_distance
182
+ end
183
+
184
+
185
+ def calculate_cost
186
+ g = get_g
187
+ h = get_h
188
+ @cost = g + h
189
+ end
190
+
191
+
192
+ def swap(i, j, new_i, new_j)
193
+ swapped_matrix = @matrix.clone
194
+ val = swapped_matrix[i,j]
195
+ swapped_matrix[i, j] = swapped_matrix[new_i, new_j]
196
+ swapped_matrix[new_i, new_j] = val
197
+ return swapped_matrix
198
+ end
199
+
200
+
201
+ # Get swapped matrix, check him in closed and open list
202
+ def moved_matrix( i, j, new_i, new_j, open_list, closed_list )
203
+ return nil if !index_exist?( new_i, new_j )
204
+
205
+ swapped_matrix = swap( i, j, new_i, new_j )
206
+
207
+ new_depth = @depth + 1
208
+ swapped_matrix = GameMatrix.new( swapped_matrix, self, new_depth )
209
+ return nil if closed_list.include?( swapped_matrix )
210
+ swapped_matrix.calculate_cost
211
+
212
+ open_list_matrix = open_list.find{ |e| e == swapped_matrix }
213
+ if open_list_matrix && open_list_matrix.cost < swapped_matrix.cost
214
+ return open_list_matrix
215
+ elsif open_list_matrix
216
+ open_list_matrix.parent = self
217
+ open_list_matrix.depth = new_depth
218
+ return open_list_matrix
219
+ else
220
+ return swapped_matrix
221
+ end
222
+ end
223
+
224
+
225
+ # Get all possible movement matrixes
226
+ def neighbors( open_list=[], closed_list=[] )
227
+ i,j = free_cell
228
+ up = moved_matrix(i, j, i-1, j, open_list, closed_list)
229
+ down = moved_matrix(i, j, i+1, j, open_list, closed_list)
230
+ left = moved_matrix(i, j, i, j-1, open_list, closed_list)
231
+ right = moved_matrix(i, j, i, j+1, open_list, closed_list)
232
+
233
+ moved = []
234
+ moved << up if !up.nil?
235
+ moved << down if !down.nil?
236
+ moved << left if !left.nil?
237
+ moved << right if !right.nil?
238
+
239
+ return moved
240
+ end
241
+ end
242
+
243
+
244
+ end
245
+
246
+
@@ -0,0 +1,133 @@
1
+ require 'rspec'
2
+ require 'fifteen_puzzle'
3
+
4
+ include FifteenPuzzle
5
+
6
+ describe GameMatrix do
7
+
8
+ it "should == to another game matrix" do
9
+ matrix = GameMatrix.new Matrix[[ 1, 4, 14, 13],
10
+ [ 11, 9, 8, 12],
11
+ [ 3, 6, GameMatrix::FREE_CELL, 7],
12
+ [15, 5, 2, 10]]
13
+
14
+ matrix2 = GameMatrix.new Matrix[[ 1, 4, 14, 13],
15
+ [ 11, 9, 8, 12],
16
+ [ 3, 6, GameMatrix::FREE_CELL, 7],
17
+ [15, 5, 2, 10]]
18
+ matrix.should == matrix2
19
+ end
20
+
21
+ it "should show next moves" do
22
+ matrix = GameMatrix.new Matrix[[ 1, 4, 14, 13],
23
+ [ 11, 9, 8, 12],
24
+ [ 3, 6, 0, 7],
25
+ [15, 5, 2, 10]]
26
+ matrix_up = GameMatrix.new Matrix[[ 1, 4, 14, 13],
27
+ [ 11, 9, 0, 12],
28
+ [ 3, 6, 8, 7],
29
+ [15, 5, 2, 10]], matrix,1
30
+ matrix_down = GameMatrix.new Matrix[[ 1, 4, 14, 13],
31
+ [ 11, 9, 8, 12],
32
+ [ 3, 6, 2, 7],
33
+ [15, 5, 0, 10]], matrix,1
34
+
35
+ matrix_left = GameMatrix.new Matrix[[ 1, 4, 14, 13],
36
+ [ 11, 9, 8, 12],
37
+ [ 3, 0, 6, 7],
38
+ [15, 5, 2, 10]], matrix,1
39
+ matrix_right = GameMatrix.new Matrix[[ 1, 4, 14, 13],
40
+ [ 11, 9, 8, 12],
41
+ [ 3, 6, 7, 0],
42
+ [15, 5, 2, 10]], matrix,1
43
+
44
+
45
+ matrix.parents_count.should == 0
46
+ matrix_up.parents_count.should == 1
47
+
48
+ matrix_up.calculate_cost
49
+ matrix_down.calculate_cost
50
+ matrix_left.calculate_cost
51
+ matrix_right.calculate_cost
52
+
53
+ matrix.neighbors([],[]).collect{|e| e.matrix}.should == [matrix_up, matrix_down, matrix_left, matrix_right].collect{|e| e.matrix}
54
+ end
55
+
56
+ it "should return swapped matrix" do
57
+ matrix = GameMatrix.new Matrix[[ 1, 4, 14, 13],
58
+ [ 11, 9, 8, 12],
59
+ [ 3, 6, 7, 0],
60
+ [15, 5, 2, 10]], matrix
61
+ matrix.moved_matrix(2,3,3,3,[], []).should == GameMatrix.new(Matrix[[ 1, 4, 14, 13],
62
+ [ 11, 9, 8, 12],
63
+ [ 3, 6, 7, 10],
64
+ [15, 5, 2, 0]], matrix )
65
+ end
66
+
67
+ it "should solve simplest 15 " do
68
+ matrix1 = Matrix[[ 1, 2, 3, 4],
69
+ [ 5, 6, 7, 8],
70
+ [ 9, 10, 11, 12],
71
+ [ 13, 14, 0, 15]]
72
+
73
+ matrix2 = Matrix[[ 1, 2, 3, 0],
74
+ [ 5, 6, 7, 4],
75
+ [ 9, 10, 11, 8],
76
+ [ 13, 14, 15, 12]]
77
+
78
+ matrix3 = Matrix[[ 1, 2, 3, 4],
79
+ [ 5, 6, 7, 8],
80
+ [ 9, 10, 12, 15],
81
+ [ 13, 14, 11, 0]]
82
+
83
+ matrix5 = Matrix[[ 1, 2, 3, 4],
84
+ [ 5, 6, 7, 8],
85
+ [ 9, 10, 0, 12],
86
+ [ 13, 14, 11, 15]]
87
+
88
+ game_matrix = GameMatrix.new( matrix5 )
89
+ algorithm = AStarAlgorithm.new( game_matrix )
90
+ algorithm.run.should == true
91
+
92
+ game_matrix = GameMatrix.new( matrix2 )
93
+ algorithm = AStarAlgorithm.new( game_matrix )
94
+ algorithm.run.should == true
95
+
96
+ game_matrix = GameMatrix.new( matrix1 )
97
+ algorithm = AStarAlgorithm.new( game_matrix )
98
+ algorithm.run.should == true
99
+
100
+ game_matrix = GameMatrix.new( matrix3 )
101
+ algorithm = AStarAlgorithm.new( game_matrix )
102
+ algorithm.run.should == true
103
+
104
+ end
105
+
106
+ it "should calc manhattan distance" do
107
+ h_matrix = GameMatrix.new Matrix[[15,1,2,3],[4,5,6,7],[8,9,10,11],[0,13,14,12]]
108
+ h_matrix.get_h.should == 28
109
+ end
110
+
111
+ it "should solve hard example" do
112
+ matrix = Matrix[[1,3,4,8],[5,2,0,12],[11,7,6,14],[10,9,15,13]]
113
+ matrix2 = Matrix[[6,4,8,7],[2,1,3,10],[5,0,9,12],[13,14,11,15]]
114
+ matrix3 = Matrix[[1, 0, 2, 3],[5, 6, 7, 4],
115
+ [9, 10, 11, 8],
116
+ [13, 14, 15, 12]
117
+ ]
118
+ matrix4= Matrix[[2,6,0,8],[1,9,7,4],[5,10,15,3],[13,12,14,11]]
119
+ game_matrix = GameMatrix.new( matrix2 )
120
+ algorithm = AStarAlgorithm.new( game_matrix )
121
+ algorithm.run.should == true
122
+ end
123
+
124
+ it "should solve Marina example" do
125
+ # matrix = Matrix[[1, 2, 3], [4, 8, 5], [7,6,0]]
126
+ # game_matrix = GameMatrix.new( matrix2 )
127
+ # algorithm = AStarAlgorithm.new( game_matrix )
128
+ # algorithm.run.should == true
129
+ end
130
+
131
+
132
+
133
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fifteen_puzzle
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - MO-524a
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-09-15 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ description: 15puzzle solver with A* algorithm
31
+ email:
32
+ - mo124a@ya.ru
33
+ executables: []
34
+ extensions: []
35
+ extra_rdoc_files: []
36
+ files:
37
+ - .gitignore
38
+ - Gemfile
39
+ - LICENSE
40
+ - README.md
41
+ - Rakefile
42
+ - fifteen_puzzle.gemspec
43
+ - lib/fifteen_puzzle.rb
44
+ - lib/fifteen_puzzle/version.rb
45
+ - spec/game_matrix_spec.rb
46
+ homepage: https://github.com/sld/astar-15puzzle-solver
47
+ licenses: []
48
+ post_install_message:
49
+ rdoc_options: []
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ! '>='
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ! '>='
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ requirements: []
65
+ rubyforge_project:
66
+ rubygems_version: 1.8.24
67
+ signing_key:
68
+ specification_version: 3
69
+ summary: 15puzzle solver with A* algorithm
70
+ test_files:
71
+ - spec/game_matrix_spec.rb