knights_tour 0.3.5
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.
- data/CHANGELOG.rdoc +50 -0
- data/MIT-LICENSE.txt +19 -0
- data/README.rdoc +66 -0
- data/Rakefile +94 -0
- data/bin/knights_tour +40 -0
- data/lib/knights_tour.rb +172 -0
- data/spec/knights_tour_spec.rb +172 -0
- metadata +78 -0
data/CHANGELOG.rdoc
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
=== 0.3.5 released 2009-09-16
|
2
|
+
|
3
|
+
* Display proper version info on Ruby 1.8 when using option "-v".
|
4
|
+
|
5
|
+
=== 0.3.4 released 2009-09-03
|
6
|
+
|
7
|
+
* Slight documentation improvements.
|
8
|
+
* Removed dependencies to Rubygems.
|
9
|
+
|
10
|
+
=== 0.3.3 released 2009-04-17
|
11
|
+
|
12
|
+
* Added missing Rakefile to the distribution gem.
|
13
|
+
* Removed rspec as a dependency gem.
|
14
|
+
|
15
|
+
=== 0.3.2 released 2009-03-31
|
16
|
+
|
17
|
+
* Slight documentation improvements.
|
18
|
+
|
19
|
+
=== 0.3.1 released 2009-03-28
|
20
|
+
|
21
|
+
* Fixed usage documentation.
|
22
|
+
|
23
|
+
=== 0.3.0 released 2009-03-28
|
24
|
+
|
25
|
+
* The software is available as a RubyGem from GitHub.
|
26
|
+
|
27
|
+
=== 0.2.5 released 2009-01-14
|
28
|
+
|
29
|
+
* Small code beautifications.
|
30
|
+
|
31
|
+
=== 0.2.4 released 2008-12-10
|
32
|
+
|
33
|
+
* Small code optimization.
|
34
|
+
|
35
|
+
=== 0.2.3 released 2008-12-11
|
36
|
+
|
37
|
+
* Small documentation improvements.
|
38
|
+
* Refactored code.
|
39
|
+
|
40
|
+
=== 0.2.2 released 2008-12-10
|
41
|
+
|
42
|
+
* Small socumentation improvements.
|
43
|
+
|
44
|
+
=== 0.2.1 released 2008-12-10
|
45
|
+
|
46
|
+
* Refactored code.
|
47
|
+
|
48
|
+
=== 0.2.0 released 2008-12-10
|
49
|
+
|
50
|
+
* First revision that solves the problem correctly. :)
|
data/MIT-LICENSE.txt
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2008-2009 Tuomas Kareinen.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
= Knight's Tour
|
2
|
+
|
3
|
+
A program that attempts to find a solution to the
|
4
|
+
{Knight's Tour problem}[http://en.wikipedia.org/wiki/Knight%27s_Tour].
|
5
|
+
I was inspired to do this by finding
|
6
|
+
{a Python implementation}[http://ttsiodras.googlepages.com/knightstour.html].
|
7
|
+
|
8
|
+
The program's algorithm is a recursive backtracking search that returns the
|
9
|
+
first solution to the problem (if the algorithm finds one). It utilizes
|
10
|
+
{Warnsdorff's heuristics}[http://mathworld.wolfram.com/KnightsTour.html] to
|
11
|
+
avoid dead ends, making the search faster in general.
|
12
|
+
|
13
|
+
== Installation
|
14
|
+
|
15
|
+
Install Knight's Tour as a RubyGem from GitHub:
|
16
|
+
|
17
|
+
$ sudo gem install tuomas-knights_tour --source http://gems.github.com
|
18
|
+
|
19
|
+
The program is compatible with Ruby 1.9.1.
|
20
|
+
|
21
|
+
== Usage
|
22
|
+
|
23
|
+
In the command line, run the program by entering
|
24
|
+
|
25
|
+
$ knights_tour
|
26
|
+
|
27
|
+
The command attempts to solve the problem on a board of size 8x8, the knight
|
28
|
+
located initially at position 0,0 (the top-left corner). If the program
|
29
|
+
finds a solution, it displays a result similar to the following:
|
30
|
+
|
31
|
+
+---+---+---+---+---+---+---+---+
|
32
|
+
| 1| 62| 13| 36| 3| 38| 31| 28|
|
33
|
+
+---+---+---+---+---+---+---+---+
|
34
|
+
| 14| 35| 2| 63| 32| 29| 4| 39|
|
35
|
+
+---+---+---+---+---+---+---+---+
|
36
|
+
| 61| 12| 59| 34| 37| 42| 27| 30|
|
37
|
+
+---+---+---+---+---+---+---+---+
|
38
|
+
| 50| 15| 64| 43| 58| 33| 40| 5|
|
39
|
+
+---+---+---+---+---+---+---+---+
|
40
|
+
| 11| 60| 49| 54| 41| 24| 45| 26|
|
41
|
+
+---+---+---+---+---+---+---+---+
|
42
|
+
| 16| 51| 18| 57| 44| 55| 6| 23|
|
43
|
+
+---+---+---+---+---+---+---+---+
|
44
|
+
| 19| 10| 53| 48| 21| 8| 25| 46|
|
45
|
+
+---+---+---+---+---+---+---+---+
|
46
|
+
| 52| 17| 20| 9| 56| 47| 22| 7|
|
47
|
+
+---+---+---+---+---+---+---+---+
|
48
|
+
|
49
|
+
The command above is the same as invoking the program with
|
50
|
+
|
51
|
+
$ knights_tour -s 0,0 8,8
|
52
|
+
|
53
|
+
The size of the board and the start position of the knight are configurable,
|
54
|
+
however. For all the options, see
|
55
|
+
|
56
|
+
$ knights_tour -h
|
57
|
+
|
58
|
+
== Contacting
|
59
|
+
|
60
|
+
Please send feedback by email to Tuomas Kareinen < tkareine (at) gmail (dot)
|
61
|
+
com >.
|
62
|
+
|
63
|
+
== Legal notes
|
64
|
+
|
65
|
+
Copyright (c) 2008-2009 Tuomas Kareinen. See MIT-LICENSE.txt in this
|
66
|
+
directory.
|
data/Rakefile
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "lib"))
|
2
|
+
|
3
|
+
require "rubygems"
|
4
|
+
|
5
|
+
full_name = "Knight's Tour"
|
6
|
+
package_name = "knights_tour"
|
7
|
+
require "#{package_name}"
|
8
|
+
version = KnightsTour::VERSION
|
9
|
+
|
10
|
+
require "rake/clean"
|
11
|
+
|
12
|
+
require "rake/gempackagetask"
|
13
|
+
spec = Gem::Specification.new do |s|
|
14
|
+
s.name = package_name
|
15
|
+
s.version = version
|
16
|
+
s.homepage = "http://github.com/tuomas/knights_tour"
|
17
|
+
s.summary = "Solves Knight's Tour problem."
|
18
|
+
s.description = "A program that attempts to find a solution to the Knight's Tour problem."
|
19
|
+
|
20
|
+
s.author = "Tuomas Kareinen"
|
21
|
+
s.email = "tkareine@gmail.com"
|
22
|
+
|
23
|
+
s.platform = Gem::Platform::RUBY
|
24
|
+
s.files = FileList["Rakefile", "MIT-LICENSE.txt", "*.rdoc", "bin/**/*", "lib/**/*", "spec/**/*"].to_a
|
25
|
+
s.executables = ["knights_tour"]
|
26
|
+
|
27
|
+
s.add_dependency("trollop", ">= 1.10.0")
|
28
|
+
|
29
|
+
s.has_rdoc = true
|
30
|
+
s.extra_rdoc_files = FileList["MIT-LICENSE.txt", "*.rdoc"].to_a
|
31
|
+
s.rdoc_options << "--title" << "#{full_name} #{version}" \
|
32
|
+
<< "--main" << "README.rdoc" \
|
33
|
+
<< "--exclude" << "spec" \
|
34
|
+
<< "--line-numbers"
|
35
|
+
end
|
36
|
+
|
37
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
38
|
+
pkg.need_zip = false
|
39
|
+
pkg.need_tar = true
|
40
|
+
end
|
41
|
+
|
42
|
+
desc "Generate a gemspec file"
|
43
|
+
task :gemspec do
|
44
|
+
File.open("#{spec.name}.gemspec", "w") do |f|
|
45
|
+
f.write spec.to_ruby
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
task :install => [:package] do
|
50
|
+
sh %{sudo gem install pkg/#{package_name}-#{version}.gem}
|
51
|
+
end
|
52
|
+
|
53
|
+
task :uninstall => [:clean] do
|
54
|
+
sh %{sudo gem uninstall #{package_name}}
|
55
|
+
end
|
56
|
+
|
57
|
+
require "rake/rdoctask"
|
58
|
+
desc "Create documentation"
|
59
|
+
Rake::RDocTask.new(:rdoc) do |rd|
|
60
|
+
rd.rdoc_dir = "rdoc"
|
61
|
+
rd.title = "#{full_name} #{version}"
|
62
|
+
rd.main = "README.rdoc"
|
63
|
+
rd.rdoc_files.include("MIT-LICENSE.txt", "*.rdoc", "lib/**/*.rb")
|
64
|
+
rd.options << "--line-numbers"
|
65
|
+
end
|
66
|
+
|
67
|
+
require "spec/rake/spectask"
|
68
|
+
desc "Run specs"
|
69
|
+
Spec::Rake::SpecTask.new(:spec) do |t|
|
70
|
+
t.spec_files = FileList["spec/**/*_spec.rb"]
|
71
|
+
t.spec_opts = ["--colour --format progress --loadby mtime"]
|
72
|
+
t.warning = true
|
73
|
+
t.libs << "lib"
|
74
|
+
end
|
75
|
+
|
76
|
+
desc "Run specs with RCov"
|
77
|
+
Spec::Rake::SpecTask.new(:rcov) do |t|
|
78
|
+
t.spec_files = FileList["spec/**/*.rb"]
|
79
|
+
t.rcov = true
|
80
|
+
t.rcov_opts = ["--exclude", "spec"]
|
81
|
+
t.libs << "lib"
|
82
|
+
end
|
83
|
+
|
84
|
+
desc "Find code smells"
|
85
|
+
task :roodi do
|
86
|
+
sh %{roodi "**/*.rb"}
|
87
|
+
end
|
88
|
+
|
89
|
+
desc "Search unfinished parts of source code"
|
90
|
+
task :todo do
|
91
|
+
FileList["**/*.rb", "**/*.rdoc", "**/*.txt"].egrep /(TODO|FIXME)/
|
92
|
+
end
|
93
|
+
|
94
|
+
task :default => :spec
|
data/bin/knights_tour
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "trollop"
|
4
|
+
require "knights_tour"
|
5
|
+
|
6
|
+
# Parse the command line arguments and invoke the application.
|
7
|
+
|
8
|
+
include KnightsTour
|
9
|
+
|
10
|
+
options = Trollop::options do
|
11
|
+
version "#{File.basename($0)} #{KnightsTour::VERSION}"
|
12
|
+
|
13
|
+
banner <<-EOS
|
14
|
+
A program that attempts to find a solution to the Knight's Tour problem.
|
15
|
+
|
16
|
+
Usage:
|
17
|
+
|
18
|
+
#{File.basename($0)} [OPTIONS] [SIZE]
|
19
|
+
|
20
|
+
Where SIZE (default: 8,8) sets the size of the board to ROWS x COLS.
|
21
|
+
|
22
|
+
Options:
|
23
|
+
EOS
|
24
|
+
|
25
|
+
opt :start_at, "Set the initial position of the knight (default: 0,0)",
|
26
|
+
:type => :string,
|
27
|
+
:required => false
|
28
|
+
end
|
29
|
+
|
30
|
+
begin
|
31
|
+
app_params = {}
|
32
|
+
app_params[:size] = ARGV.shift unless ARGV.empty?
|
33
|
+
app_params[:start_at] = options[:start_at] if options[:start_at]
|
34
|
+
|
35
|
+
app = Application.new(app_params)
|
36
|
+
rescue ArgumentError => e
|
37
|
+
Trollop::die e.to_s
|
38
|
+
end
|
39
|
+
|
40
|
+
puts app.solve
|
data/lib/knights_tour.rb
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
module KnightsTour
|
2
|
+
VERSION = "0.3.5"
|
3
|
+
|
4
|
+
class Application
|
5
|
+
def initialize(params = {})
|
6
|
+
@board_size = parse_board_size(params[:size] || [8, 8])
|
7
|
+
@knight_starts_at = parse_position_on_board(
|
8
|
+
params[:start_at] || [0, 0],
|
9
|
+
@board_size)
|
10
|
+
end
|
11
|
+
|
12
|
+
def solve
|
13
|
+
@solution ||= StringResult.new(traverse(
|
14
|
+
Knight.new(@board_size, @knight_starts_at)))
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def parse_pair(param)
|
20
|
+
unless param.is_a?(Array)
|
21
|
+
param = param.to_s.split(",")
|
22
|
+
end
|
23
|
+
[param[0].to_i, param[1].to_i]
|
24
|
+
end
|
25
|
+
|
26
|
+
def parse_board_size(size)
|
27
|
+
size = parse_pair(size)
|
28
|
+
unless size[0] > 0 && size[1] > 0
|
29
|
+
raise ArgumentError,
|
30
|
+
"Board size must be a pair of positive (non-zero) " \
|
31
|
+
"integers, separated by a comma"
|
32
|
+
end
|
33
|
+
size
|
34
|
+
end
|
35
|
+
|
36
|
+
def parse_position_on_board(position, board_size)
|
37
|
+
position = parse_pair(position)
|
38
|
+
unless (0...board_size[0]).include?(position[0]) &&
|
39
|
+
(0...board_size[1]).include?(position[1])
|
40
|
+
raise ArgumentError,
|
41
|
+
"Position must be a pair of positive integers within the " \
|
42
|
+
"size limits of the board, separated by a comma " \
|
43
|
+
"(for example, 0,5 is acceptable for board size 6,6)"
|
44
|
+
end
|
45
|
+
position
|
46
|
+
end
|
47
|
+
|
48
|
+
# Traverse the knight on the board.
|
49
|
+
#
|
50
|
+
# The algorithm is a recursive backtracking search for a first solution
|
51
|
+
# to the problem. The board is copied and modified by moving the knight
|
52
|
+
# to a new position in each recursive step of the algorithm, instead of
|
53
|
+
# modifying a single shared board in place.
|
54
|
+
def traverse(knight)
|
55
|
+
unless knight.traversed?
|
56
|
+
next_positions = knight.find_next_positions
|
57
|
+
next_positions.each do |next_position|
|
58
|
+
knight = traverse(knight.dup.traverse_to(next_position))
|
59
|
+
unless knight.nil?
|
60
|
+
return knight # return the first solution found
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
knight # no solutions found, or already found one
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class Knight
|
70
|
+
## as [x, y] pairs
|
71
|
+
LEGAL_STEPS = [ [-2, 1], [-1, 2], [ 1, 2], [ 2, 1],
|
72
|
+
[ 2, -1], [ 1, -2], [-1, -2], [-2, -1] ]
|
73
|
+
|
74
|
+
attr_reader :board, :steps_taken, :current_position
|
75
|
+
|
76
|
+
def initialize(board_size, start_at)
|
77
|
+
@board = Array.new(board_size[0]) { Array.new(board_size[1], 0) }
|
78
|
+
@steps_taken = 0
|
79
|
+
traverse_to(start_at)
|
80
|
+
end
|
81
|
+
|
82
|
+
def initialize_copy(other)
|
83
|
+
@board = Marshal.load(Marshal.dump(other.board))
|
84
|
+
@steps_taken = other.steps_taken
|
85
|
+
end
|
86
|
+
|
87
|
+
def traversed?
|
88
|
+
last_step = @board.size * @board[0].size
|
89
|
+
@steps_taken == last_step
|
90
|
+
end
|
91
|
+
|
92
|
+
def traverse_to(new_position)
|
93
|
+
@steps_taken += 1
|
94
|
+
@current_position = new_position
|
95
|
+
@board[@current_position[0]][@current_position[1]] = @steps_taken
|
96
|
+
self
|
97
|
+
end
|
98
|
+
|
99
|
+
def find_next_positions
|
100
|
+
sort_by_warnsdorffs_heuristics(find_next_positions_at(@current_position))
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
# Optimization by applying Warnsdorff's heuristics: attempt to avoid
|
106
|
+
# dead ends by favoring positions with the lowest number of next
|
107
|
+
# available positions (thus, isolated positions become visited first).
|
108
|
+
#
|
109
|
+
# References:
|
110
|
+
# <http://mathworld.wolfram.com/KnightsTour.html>
|
111
|
+
# <http://web.telia.com/~u85905224/knight/eWarnsd.htm>
|
112
|
+
def sort_by_warnsdorffs_heuristics(positions)
|
113
|
+
positions.sort_by do |position|
|
114
|
+
find_next_positions_at(position).size
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def find_next_positions_at(position)
|
119
|
+
positions = LEGAL_STEPS.map do |step|
|
120
|
+
position_after_step(position, step)
|
121
|
+
end
|
122
|
+
positions.reject { |pos| pos.nil? || (@board[pos[0]][pos[1]] > 0) }
|
123
|
+
end
|
124
|
+
|
125
|
+
def position_after_step(from, step)
|
126
|
+
x_pos = from[0] + step[0]
|
127
|
+
y_pos = from[1] + step[1]
|
128
|
+
|
129
|
+
if (0...@board.size).include?(x_pos) &&
|
130
|
+
(0...@board[0].size).include?(y_pos)
|
131
|
+
[x_pos, y_pos]
|
132
|
+
else
|
133
|
+
nil
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
class StringResult
|
139
|
+
def initialize(result)
|
140
|
+
if result.is_a?(Knight)
|
141
|
+
@result = board_to_s(result.board, result.steps_taken)
|
142
|
+
else
|
143
|
+
@result = "No solution found."
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def to_s
|
148
|
+
@result
|
149
|
+
end
|
150
|
+
|
151
|
+
private
|
152
|
+
|
153
|
+
def board_to_s(board, steps_taken)
|
154
|
+
square_width = steps_taken.to_s.length + 1
|
155
|
+
separator_str = separator(square_width, board[0].size)
|
156
|
+
|
157
|
+
output = ""
|
158
|
+
|
159
|
+
board.each do |row|
|
160
|
+
output << separator_str
|
161
|
+
row_output = row.map { |step| "%#{square_width}s" % step }.join("|")
|
162
|
+
output << "|#{row_output}|\n"
|
163
|
+
end
|
164
|
+
|
165
|
+
output << separator_str
|
166
|
+
end
|
167
|
+
|
168
|
+
def separator(board_width, cols)
|
169
|
+
("+" << "-" * board_width) * cols << "+\n"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
require "knights_tour"
|
2
|
+
|
3
|
+
include KnightsTour
|
4
|
+
|
5
|
+
describe KnightsTour::Application do
|
6
|
+
it "should accept valid non-default board size" do
|
7
|
+
lambda { Application.new(:size => -1) }.should raise_error(ArgumentError)
|
8
|
+
lambda { Application.new(:size => 0) }.should raise_error(ArgumentError)
|
9
|
+
lambda { Application.new(:size => 1) }.should raise_error(ArgumentError)
|
10
|
+
lambda { Application.new(:size => "1") }.should raise_error(ArgumentError)
|
11
|
+
lambda { Application.new(:size => [-1, -1]) }.should raise_error(ArgumentError)
|
12
|
+
lambda { Application.new(:size => [-1, 0]) }.should raise_error(ArgumentError)
|
13
|
+
lambda { Application.new(:size => [0, -1]) }.should raise_error(ArgumentError)
|
14
|
+
lambda { Application.new(:size => [0, 0]) }.should raise_error(ArgumentError)
|
15
|
+
lambda { Application.new(:size => [0, 1]) }.should raise_error(ArgumentError)
|
16
|
+
lambda { Application.new(:size => [1, 0]) }.should raise_error(ArgumentError)
|
17
|
+
lambda { Application.new(:size => [1, 1]) }.should_not raise_error(ArgumentError)
|
18
|
+
lambda { Application.new(:size => [3, 4]) }.should_not raise_error(ArgumentError)
|
19
|
+
lambda { Application.new(:size => [5, 5]) }.should_not raise_error(ArgumentError)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should accept valid non-default start positions" do
|
23
|
+
lambda { Application.new(:size => [1, 1], :start_at => -1) }.should raise_error(ArgumentError)
|
24
|
+
lambda { Application.new(:size => [1, 1], :start_at => [ 0, 1]) }.should raise_error(ArgumentError)
|
25
|
+
lambda { Application.new(:size => [1, 1], :start_at => [ 1, 0]) }.should raise_error(ArgumentError)
|
26
|
+
lambda { Application.new(:size => [1, 1], :start_at => [ 1, 1]) }.should raise_error(ArgumentError)
|
27
|
+
lambda { Application.new(:size => [1, 1], :start_at => [ 3, 4]) }.should raise_error(ArgumentError)
|
28
|
+
lambda { Application.new(:size => [3, 7], :start_at => [ 4, 2]) }.should raise_error(ArgumentError)
|
29
|
+
lambda { Application.new(:size => [3, 7], :start_at => [ 2, 7]) }.should raise_error(ArgumentError)
|
30
|
+
lambda { Application.new(:size => [3, 7], :start_at => [ 3, 7]) }.should raise_error(ArgumentError)
|
31
|
+
lambda { Application.new(:size => [1, 1], :start_at => [ 0, 0]) }.should_not raise_error(ArgumentError)
|
32
|
+
lambda { Application.new(:size => [1, 2], :start_at => [ 0, 1]) }.should_not raise_error(ArgumentError)
|
33
|
+
lambda { Application.new(:size => [2, 1], :start_at => [ 1, 0]) }.should_not raise_error(ArgumentError)
|
34
|
+
lambda { Application.new(:size => [2, 2], :start_at => [ 0, 0]) }.should_not raise_error(ArgumentError)
|
35
|
+
lambda { Application.new(:size => [2, 2], :start_at => [ 1, 1]) }.should_not raise_error(ArgumentError)
|
36
|
+
lambda { Application.new(:size => [3, 7], :start_at => [ 2, 4]) }.should_not raise_error(ArgumentError)
|
37
|
+
lambda { Application.new(:size => [5, 5], :start_at => [ 4, 4]) }.should_not raise_error(ArgumentError)
|
38
|
+
lambda { Application.new(:size => [8, 8], :start_at => [ 7, 6]) }.should_not raise_error(ArgumentError)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should solve a board with size 1,1" do
|
42
|
+
result = Application.new(:size => [1, 1]).solve
|
43
|
+
result.to_s.should == <<-END
|
44
|
+
+--+
|
45
|
+
| 1|
|
46
|
+
+--+
|
47
|
+
END
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should solve a board with size 5,5" do
|
51
|
+
result = Application.new(:size => [5, 5]).solve
|
52
|
+
result.to_s.should == <<-END
|
53
|
+
+---+---+---+---+---+
|
54
|
+
| 1| 14| 9| 20| 3|
|
55
|
+
+---+---+---+---+---+
|
56
|
+
| 24| 19| 2| 15| 10|
|
57
|
+
+---+---+---+---+---+
|
58
|
+
| 13| 8| 25| 4| 21|
|
59
|
+
+---+---+---+---+---+
|
60
|
+
| 18| 23| 6| 11| 16|
|
61
|
+
+---+---+---+---+---+
|
62
|
+
| 7| 12| 17| 22| 5|
|
63
|
+
+---+---+---+---+---+
|
64
|
+
END
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should solve a board with size of 5,5, in start position 2,2" do
|
68
|
+
result = Application.new(:size => [5, 5], :start_at => [2, 2]).solve
|
69
|
+
result.to_s.should == <<-END
|
70
|
+
+---+---+---+---+---+
|
71
|
+
| 21| 12| 7| 2| 19|
|
72
|
+
+---+---+---+---+---+
|
73
|
+
| 6| 17| 20| 13| 8|
|
74
|
+
+---+---+---+---+---+
|
75
|
+
| 11| 22| 1| 18| 3|
|
76
|
+
+---+---+---+---+---+
|
77
|
+
| 16| 5| 24| 9| 14|
|
78
|
+
+---+---+---+---+---+
|
79
|
+
| 23| 10| 15| 4| 25|
|
80
|
+
+---+---+---+---+---+
|
81
|
+
END
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should solve a board with size of 3,7, in start position 2,4" do
|
85
|
+
result = Application.new(:size => [3, 7], :start_at => [2, 4]).solve
|
86
|
+
result.to_s.should == <<-END
|
87
|
+
+---+---+---+---+---+---+---+
|
88
|
+
| 11| 14| 17| 20| 3| 8| 5|
|
89
|
+
+---+---+---+---+---+---+---+
|
90
|
+
| 16| 21| 12| 9| 6| 19| 2|
|
91
|
+
+---+---+---+---+---+---+---+
|
92
|
+
| 13| 10| 15| 18| 1| 4| 7|
|
93
|
+
+---+---+---+---+---+---+---+
|
94
|
+
END
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should cache the result" do
|
98
|
+
app = Application.new(:size => [1, 1])
|
99
|
+
result = []
|
100
|
+
result << app.solve
|
101
|
+
result << app.solve
|
102
|
+
result[0].should == result[1]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe KnightsTour::Knight do
|
107
|
+
before(:each) do
|
108
|
+
@knight = Knight.new([5, 5], [0, 0])
|
109
|
+
# broken board state, but it does not matter for testing
|
110
|
+
@knight.instance_variable_set(
|
111
|
+
:@board,
|
112
|
+
[ [ 1, 0, 0, 12, 3 ],
|
113
|
+
[ 0, 11, 2, 7, 18 ],
|
114
|
+
[ 0, 0, 0, 0, 13 ],
|
115
|
+
[ 10, 15, 6, 0, 8 ],
|
116
|
+
[ 0, 19, 9, 14, 5 ] ])
|
117
|
+
@knight.instance_variable_set(:@current_position, [1, 4])
|
118
|
+
@knight.instance_variable_set(:@steps_taken, 18)
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should find next positions available" do
|
122
|
+
next_positions = @knight.send(:find_next_positions_at, @knight.current_position)
|
123
|
+
next_positions.sort! # ensure the order is not correct already
|
124
|
+
next_positions.should == [ [0, 2], [2, 2], [3, 3] ]
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should sort next positions by Warnsdorff's heuristics" do
|
128
|
+
next_positions = @knight.send(:find_next_positions_at, @knight.current_position)
|
129
|
+
next_positions.sort! # ensure the order is not correct already
|
130
|
+
next_positions.should == [ [0, 2], [2, 2], [3, 3] ]
|
131
|
+
next_positions = @knight.send(:sort_by_warnsdorffs_heuristics, next_positions)
|
132
|
+
next_positions.should == [ [3, 3], [2, 2], [0, 2] ]
|
133
|
+
end
|
134
|
+
|
135
|
+
it "should find next positions, sorted" do
|
136
|
+
next_positions = @knight.find_next_positions
|
137
|
+
next_positions.should == [ [3, 3], [2, 2], [0, 2] ]
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe KnightsTour::StringResult do
|
142
|
+
it "should show the result correctly for a failed result" do
|
143
|
+
StringResult.new(nil).to_s.should == "No solution found."
|
144
|
+
end
|
145
|
+
|
146
|
+
it "should show the result correctly for a trivial result" do
|
147
|
+
knight = Knight.new([1, 1], [0, 0])
|
148
|
+
knight.instance_variable_set(:@board, [[1]])
|
149
|
+
result = StringResult.new(knight)
|
150
|
+
result.to_s.should == <<-END
|
151
|
+
+--+
|
152
|
+
| 1|
|
153
|
+
+--+
|
154
|
+
END
|
155
|
+
end
|
156
|
+
|
157
|
+
it "should show the result correctly for a non-trivial result" do
|
158
|
+
# in reality, this is not a solvable board size
|
159
|
+
knight = Knight.new([3, 3], [0, 0])
|
160
|
+
knight.instance_variable_set(:@board, [[1, 2, 3], [42, 56, 69], [0, 0, 119]])
|
161
|
+
knight.instance_variable_set(:@steps_taken, 119)
|
162
|
+
StringResult.new(knight).to_s.should == <<-END
|
163
|
+
+----+----+----+
|
164
|
+
| 1| 2| 3|
|
165
|
+
+----+----+----+
|
166
|
+
| 42| 56| 69|
|
167
|
+
+----+----+----+
|
168
|
+
| 0| 0| 119|
|
169
|
+
+----+----+----+
|
170
|
+
END
|
171
|
+
end
|
172
|
+
end
|
metadata
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: knights_tour
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.5
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tuomas Kareinen
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-12 00:00:00 +03:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: trollop
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.10.0
|
24
|
+
version:
|
25
|
+
description: A program that attempts to find a solution to the Knight's Tour problem.
|
26
|
+
email: tkareine@gmail.com
|
27
|
+
executables:
|
28
|
+
- knights_tour
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- MIT-LICENSE.txt
|
33
|
+
- CHANGELOG.rdoc
|
34
|
+
- README.rdoc
|
35
|
+
files:
|
36
|
+
- Rakefile
|
37
|
+
- MIT-LICENSE.txt
|
38
|
+
- CHANGELOG.rdoc
|
39
|
+
- README.rdoc
|
40
|
+
- bin/knights_tour
|
41
|
+
- lib/knights_tour.rb
|
42
|
+
- spec/knights_tour_spec.rb
|
43
|
+
has_rdoc: true
|
44
|
+
homepage: http://github.com/tuomas/knights_tour
|
45
|
+
licenses: []
|
46
|
+
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options:
|
49
|
+
- --title
|
50
|
+
- Knight's Tour 0.3.5
|
51
|
+
- --main
|
52
|
+
- README.rdoc
|
53
|
+
- --exclude
|
54
|
+
- spec
|
55
|
+
- --line-numbers
|
56
|
+
require_paths:
|
57
|
+
- lib
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: "0"
|
63
|
+
version:
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: "0"
|
69
|
+
version:
|
70
|
+
requirements: []
|
71
|
+
|
72
|
+
rubyforge_project:
|
73
|
+
rubygems_version: 1.3.5
|
74
|
+
signing_key:
|
75
|
+
specification_version: 3
|
76
|
+
summary: Solves Knight's Tour problem.
|
77
|
+
test_files: []
|
78
|
+
|