tuomas-knights_tour 0.2.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 +24 -0
- data/Manifest.txt +7 -0
- data/README.rdoc +82 -0
- data/Rakefile +48 -0
- data/bin/knights_tour +41 -0
- data/lib/knights_tour.rb +192 -0
- data/spec/knights_tour_spec.rb +172 -0
- metadata +92 -0
data/CHANGELOG.rdoc
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
=== 0.2.5 released 2009-01-14
|
2
|
+
|
3
|
+
* Small code beautifications.
|
4
|
+
|
5
|
+
=== 0.2.4 released 2008-12-10
|
6
|
+
|
7
|
+
* Small code optimization.
|
8
|
+
|
9
|
+
=== 0.2.3 released 2008-12-11
|
10
|
+
|
11
|
+
* Small documentation improvements.
|
12
|
+
* Refactored code.
|
13
|
+
|
14
|
+
=== 0.2.2 released 2008-12-10
|
15
|
+
|
16
|
+
* Small socumentation improvements.
|
17
|
+
|
18
|
+
=== 0.2.1 released 2008-12-10
|
19
|
+
|
20
|
+
* Refactored code.
|
21
|
+
|
22
|
+
=== 0.2.0 released 2008-12-10
|
23
|
+
|
24
|
+
* First revision that solves the problem correctly. :)
|
data/Manifest.txt
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,82 @@
|
|
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 the software as a RubyGem from GitHub:
|
16
|
+
|
17
|
+
$ sudo gem install tuomas-knights_tour --source http://gems.github.com
|
18
|
+
|
19
|
+
The software is compatible with Ruby 1.9.1.
|
20
|
+
|
21
|
+
== Usage
|
22
|
+
|
23
|
+
Change the working directory of your command line shell into the project's
|
24
|
+
root directory and enter
|
25
|
+
|
26
|
+
$ ./bin/knights_tour
|
27
|
+
|
28
|
+
The command attempts to solve the problem on a board of size 8x8, the knight
|
29
|
+
located initially at position 0,0 (the top-left corner). If the program
|
30
|
+
finds a solution, it displays a result similar to the following:
|
31
|
+
|
32
|
+
+---+---+---+---+---+---+---+---+
|
33
|
+
| 1| 62| 13| 36| 3| 38| 31| 28|
|
34
|
+
+---+---+---+---+---+---+---+---+
|
35
|
+
| 14| 35| 2| 63| 32| 29| 4| 39|
|
36
|
+
+---+---+---+---+---+---+---+---+
|
37
|
+
| 61| 12| 59| 34| 37| 42| 27| 30|
|
38
|
+
+---+---+---+---+---+---+---+---+
|
39
|
+
| 50| 15| 64| 43| 58| 33| 40| 5|
|
40
|
+
+---+---+---+---+---+---+---+---+
|
41
|
+
| 11| 60| 49| 54| 41| 24| 45| 26|
|
42
|
+
+---+---+---+---+---+---+---+---+
|
43
|
+
| 16| 51| 18| 57| 44| 55| 6| 23|
|
44
|
+
+---+---+---+---+---+---+---+---+
|
45
|
+
| 19| 10| 53| 48| 21| 8| 25| 46|
|
46
|
+
+---+---+---+---+---+---+---+---+
|
47
|
+
| 52| 17| 20| 9| 56| 47| 22| 7|
|
48
|
+
+---+---+---+---+---+---+---+---+
|
49
|
+
|
50
|
+
The size of the board and the start position of the knight are configurable.
|
51
|
+
For all the options, see
|
52
|
+
|
53
|
+
$ ./bin/knights_tour -h
|
54
|
+
|
55
|
+
== Contacting
|
56
|
+
|
57
|
+
Please send feedback by email to Tuomas Kareinen < tkareine (at) gmail (dot)
|
58
|
+
com >.
|
59
|
+
|
60
|
+
== Legal notes
|
61
|
+
|
62
|
+
This software is licensed under the terms of the "MIT license":
|
63
|
+
|
64
|
+
Copyright (c) 2008-2009 Tuomas Kareinen.
|
65
|
+
|
66
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
67
|
+
of this software and associated documentation files (the "Software"), to
|
68
|
+
deal in the Software without restriction, including without limitation the
|
69
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
70
|
+
sell copies of the Software, and to permit persons to whom the Software is
|
71
|
+
furnished to do so, subject to the following conditions:
|
72
|
+
|
73
|
+
The above copyright notice and this permission notice shall be included in
|
74
|
+
all copies or substantial portions of the Software.
|
75
|
+
|
76
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
77
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
78
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
79
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
80
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
81
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
82
|
+
IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
|
3
|
+
require "hoe"
|
4
|
+
require "./lib/knights_tour"
|
5
|
+
Hoe.new("knights_tour", KnightsTour::Meta::VERSION.to_s) do |p|
|
6
|
+
p.author = "Tuomas Kareinen"
|
7
|
+
p.email = "tkareine@gmail.com"
|
8
|
+
p.url = "http://github.com/tuomas/knights_tour"
|
9
|
+
p.summary =<<-END
|
10
|
+
A program that attempts to find a solution to the Knight's Tour problem.
|
11
|
+
END
|
12
|
+
p.readme_file = "README.rdoc"
|
13
|
+
p.history_file = "CHANGELOG.rdoc"
|
14
|
+
p.extra_rdoc_files = FileList["*.rdoc", "lib/**/*.rb"]
|
15
|
+
p.extra_deps = [["trollop", ">= 1.10.0"]]
|
16
|
+
p.extra_dev_deps = [["rspec", ">= 1.2.0"]]
|
17
|
+
p.rubyforge_name = "searchable-rec"
|
18
|
+
end
|
19
|
+
|
20
|
+
require "rake/rdoctask"
|
21
|
+
require "lib/knights_tour"
|
22
|
+
desc "Create documentation."
|
23
|
+
Rake::RDocTask.new(:rdoc) do |rd|
|
24
|
+
rd.title = "Knight's Tour #{KnightsTour::Meta::VERSION}"
|
25
|
+
rd.main = "README.rdoc"
|
26
|
+
rd.rdoc_files.include("*.rdoc", "lib/**/*.rb")
|
27
|
+
rd.rdoc_dir = "rdoc"
|
28
|
+
end
|
29
|
+
|
30
|
+
require "spec/rake/spectask"
|
31
|
+
desc "Run specs."
|
32
|
+
Spec::Rake::SpecTask.new(:spec) do |t|
|
33
|
+
t.spec_files = FileList["spec/**/*.rb"]
|
34
|
+
t.spec_opts = ["--format", "specdoc"]
|
35
|
+
#t.warning = true
|
36
|
+
end
|
37
|
+
|
38
|
+
desc "Find code smells."
|
39
|
+
task :roodi do
|
40
|
+
sh("roodi '**/*.rb'")
|
41
|
+
end
|
42
|
+
|
43
|
+
desc "Search unfinished parts of source code."
|
44
|
+
task :todo do
|
45
|
+
FileList["**/*.rb"].egrep /#.*(TODO|FIXME)/
|
46
|
+
end
|
47
|
+
|
48
|
+
task :default => :spec
|
data/bin/knights_tour
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "rubygems"
|
4
|
+
require "trollop"
|
5
|
+
require File.dirname(__FILE__) << "/../lib/knights_tour"
|
6
|
+
|
7
|
+
# Parse the command line arguments and invoke the application.
|
8
|
+
|
9
|
+
include KnightsTour
|
10
|
+
|
11
|
+
options = Trollop::options do
|
12
|
+
version Meta.version
|
13
|
+
|
14
|
+
banner <<-EOS
|
15
|
+
A program that attempts to find a solution to the Knight's Tour problem.
|
16
|
+
|
17
|
+
Usage:
|
18
|
+
|
19
|
+
#{File.basename($0)} [OPTIONS] [SIZE]
|
20
|
+
|
21
|
+
Where SIZE (default: 8,8) sets the size of the board to ROWS x COLS.
|
22
|
+
|
23
|
+
Options:
|
24
|
+
EOS
|
25
|
+
|
26
|
+
opt :start_at, "Set the initial position of the knight (default: 0,0)",
|
27
|
+
:type => :string,
|
28
|
+
:required => false
|
29
|
+
end
|
30
|
+
|
31
|
+
begin
|
32
|
+
app_params = {}
|
33
|
+
app_params[:size] = ARGV.shift unless ARGV.empty?
|
34
|
+
app_params[:start_at] = options[:start_at] if options[:start_at]
|
35
|
+
|
36
|
+
app = Application.new(app_params)
|
37
|
+
rescue ArgumentError => e
|
38
|
+
Trollop::die e.to_s
|
39
|
+
end
|
40
|
+
|
41
|
+
$stdout.puts app.solve
|
data/lib/knights_tour.rb
ADDED
@@ -0,0 +1,192 @@
|
|
1
|
+
module KnightsTour
|
2
|
+
module Meta #:nodoc:
|
3
|
+
module VERSION #:nodoc:
|
4
|
+
MAJOR = 0
|
5
|
+
MINOR = 2
|
6
|
+
PATCH = 5
|
7
|
+
|
8
|
+
def self.to_s
|
9
|
+
[ MAJOR, MINOR, PATCH ].join(".")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
COPYRIGHT = "Copyright (c) Tuomas Kareinen"
|
14
|
+
|
15
|
+
LICENSE = "Licensed under the terms of the \"MIT license\". See README.rdoc."
|
16
|
+
|
17
|
+
def self.version
|
18
|
+
"#{File.basename($0)} #{Meta::VERSION}\n#{Meta::COPYRIGHT}\n#{Meta::LICENSE}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Application
|
23
|
+
def initialize(params = {})
|
24
|
+
@board_size = parse_board_size(params[:size] || [8, 8])
|
25
|
+
@knight_starts_at = parse_position_on_board(
|
26
|
+
params[:start_at] || [0, 0],
|
27
|
+
@board_size)
|
28
|
+
end
|
29
|
+
|
30
|
+
def solve
|
31
|
+
@solution ||= StringResult.new(traverse(
|
32
|
+
Knight.new(@board_size, @knight_starts_at)))
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def parse_pair(param)
|
38
|
+
unless param.is_a?(Array)
|
39
|
+
param = param.to_s.split(",")
|
40
|
+
end
|
41
|
+
[param[0].to_i, param[1].to_i]
|
42
|
+
end
|
43
|
+
|
44
|
+
def parse_board_size(size)
|
45
|
+
size = parse_pair(size)
|
46
|
+
unless size[0] > 0 && size[1] > 0
|
47
|
+
raise ArgumentError,
|
48
|
+
"Board size must be a pair of positive (non-zero) " \
|
49
|
+
"integers, separated by a comma"
|
50
|
+
end
|
51
|
+
size
|
52
|
+
end
|
53
|
+
|
54
|
+
def parse_position_on_board(position, board_size)
|
55
|
+
position = parse_pair(position)
|
56
|
+
unless (0...board_size[0]).include?(position[0]) &&
|
57
|
+
(0...board_size[1]).include?(position[1])
|
58
|
+
raise ArgumentError,
|
59
|
+
"Position must be a pair of positive integers within the " \
|
60
|
+
"size limits of the board, separated by a comma " \
|
61
|
+
"(for example, 0,5 is acceptable for board size 6,6)"
|
62
|
+
end
|
63
|
+
position
|
64
|
+
end
|
65
|
+
|
66
|
+
# Traverse the knight on the board.
|
67
|
+
#
|
68
|
+
# The algorithm is a recursive backtracking search for a first solution
|
69
|
+
# to the problem. The board is copied and modified by moving the knight
|
70
|
+
# to a new position in each recursive step of the algorithm, instead of
|
71
|
+
# modifying a single shared board in place.
|
72
|
+
def traverse(knight)
|
73
|
+
#$stdout.puts StringResult.new(board) # debug
|
74
|
+
|
75
|
+
unless knight.traversed?
|
76
|
+
next_positions = knight.find_next_positions
|
77
|
+
next_positions.each do |next_position|
|
78
|
+
knight = traverse(knight.dup.traverse_to(next_position))
|
79
|
+
unless knight.nil?
|
80
|
+
return knight # return the first solution found
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
knight # no solutions found, or already found one
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class Knight
|
90
|
+
## as [x, y] pairs
|
91
|
+
LEGAL_STEPS = [ [-2, 1], [-1, 2], [ 1, 2], [ 2, 1],
|
92
|
+
[ 2, -1], [ 1, -2], [-1, -2], [-2, -1] ]
|
93
|
+
|
94
|
+
attr_reader :board, :steps_taken, :current_position
|
95
|
+
|
96
|
+
def initialize(board_size, start_at)
|
97
|
+
@board = Array.new(board_size[0]) { Array.new(board_size[1], 0) }
|
98
|
+
@steps_taken = 0
|
99
|
+
traverse_to(start_at)
|
100
|
+
end
|
101
|
+
|
102
|
+
def initialize_copy(other)
|
103
|
+
@board = Marshal.load(Marshal.dump(other.board))
|
104
|
+
@steps_taken = other.steps_taken
|
105
|
+
end
|
106
|
+
|
107
|
+
def traversed?
|
108
|
+
last_step = @board.size * @board[0].size
|
109
|
+
@steps_taken == last_step
|
110
|
+
end
|
111
|
+
|
112
|
+
def traverse_to(new_position)
|
113
|
+
@steps_taken += 1
|
114
|
+
@current_position = new_position
|
115
|
+
@board[@current_position[0]][@current_position[1]] = @steps_taken
|
116
|
+
self
|
117
|
+
end
|
118
|
+
|
119
|
+
def find_next_positions
|
120
|
+
sort_by_warnsdorffs_heuristics(find_next_positions_at(@current_position))
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
# Optimization by applying Warnsdorff's heuristics: attempt to avoid
|
126
|
+
# dead ends by favoring positions with the lowest number of next
|
127
|
+
# available positions (thus, isolated positions become visited first).
|
128
|
+
#
|
129
|
+
# References:
|
130
|
+
# <http://mathworld.wolfram.com/KnightsTour.html>
|
131
|
+
# <http://web.telia.com/~u85905224/knight/eWarnsd.htm>
|
132
|
+
def sort_by_warnsdorffs_heuristics(positions)
|
133
|
+
positions.sort_by do |position|
|
134
|
+
find_next_positions_at(position).size
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def find_next_positions_at(position)
|
139
|
+
positions = LEGAL_STEPS.map do |step|
|
140
|
+
position_after_step(position, step)
|
141
|
+
end
|
142
|
+
positions.reject { |pos| pos.nil? || (@board[pos[0]][pos[1]] > 0) }
|
143
|
+
end
|
144
|
+
|
145
|
+
def position_after_step(from, step)
|
146
|
+
x_pos = from[0] + step[0]
|
147
|
+
y_pos = from[1] + step[1]
|
148
|
+
|
149
|
+
if (0...@board.size).include?(x_pos) &&
|
150
|
+
(0...@board[0].size).include?(y_pos)
|
151
|
+
[x_pos, y_pos]
|
152
|
+
else
|
153
|
+
nil
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
class StringResult
|
159
|
+
def initialize(result)
|
160
|
+
if result.is_a?(Knight)
|
161
|
+
@result = board_to_s(result.board, result.steps_taken)
|
162
|
+
else
|
163
|
+
@result = "No solution found."
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def to_s
|
168
|
+
@result
|
169
|
+
end
|
170
|
+
|
171
|
+
private
|
172
|
+
|
173
|
+
def board_to_s(board, steps_taken)
|
174
|
+
square_width = steps_taken.to_s.length + 1
|
175
|
+
separator_str = separator(square_width, board[0].size)
|
176
|
+
|
177
|
+
output = ""
|
178
|
+
|
179
|
+
board.each do |row|
|
180
|
+
output << separator_str
|
181
|
+
row_output = row.map { |step| "%#{square_width}s" % step }.join("|")
|
182
|
+
output << "|#{row_output}|\n"
|
183
|
+
end
|
184
|
+
|
185
|
+
output << separator_str
|
186
|
+
end
|
187
|
+
|
188
|
+
def separator(board_width, cols)
|
189
|
+
("+" << "-" * board_width) * cols << "+\n"
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
require File.dirname(__FILE__) << "/../lib/knights_tour"
|
2
|
+
|
3
|
+
include KnightsTour
|
4
|
+
|
5
|
+
describe 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 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 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,92 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tuomas-knights_tour
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.5
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tuomas Kareinen
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-03-28 00:00:00 -07:00
|
13
|
+
default_executable: knights_tour
|
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
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
type: :development
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.2.0
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: hoe
|
37
|
+
type: :development
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 1.11.0
|
44
|
+
version:
|
45
|
+
description: ""
|
46
|
+
email: tkareine@gmail.com
|
47
|
+
executables:
|
48
|
+
- knights_tour
|
49
|
+
extensions: []
|
50
|
+
|
51
|
+
extra_rdoc_files:
|
52
|
+
- Manifest.txt
|
53
|
+
- CHANGELOG.rdoc
|
54
|
+
- README.rdoc
|
55
|
+
- lib/knights_tour.rb
|
56
|
+
files:
|
57
|
+
- CHANGELOG.rdoc
|
58
|
+
- Manifest.txt
|
59
|
+
- README.rdoc
|
60
|
+
- Rakefile
|
61
|
+
- bin/knights_tour
|
62
|
+
- lib/knights_tour.rb
|
63
|
+
- spec/knights_tour_spec.rb
|
64
|
+
has_rdoc: true
|
65
|
+
homepage: http://github.com/tuomas/knights_tour
|
66
|
+
post_install_message:
|
67
|
+
rdoc_options:
|
68
|
+
- --main
|
69
|
+
- README.rdoc
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: "0"
|
77
|
+
version:
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: "0"
|
83
|
+
version:
|
84
|
+
requirements: []
|
85
|
+
|
86
|
+
rubyforge_project: searchable-rec
|
87
|
+
rubygems_version: 1.2.0
|
88
|
+
signing_key:
|
89
|
+
specification_version: 2
|
90
|
+
summary: A program that attempts to find a solution to the Knight's Tour problem.
|
91
|
+
test_files: []
|
92
|
+
|