bfs_brute_force 0.1.0
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 +14 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +122 -0
- data/Rakefile +8 -0
- data/bfs_brute_force.gemspec +31 -0
- data/lib/bfs_brute_force/version.rb +3 -0
- data/lib/bfs_brute_force.rb +145 -0
- data/test/basic.rb +46 -0
- data/test/simple_puzzle.rb +91 -0
- metadata +104 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d9930b15a9a8a806ab35bd20859d81154d3cd8c2
|
4
|
+
data.tar.gz: 5f86c835cef8195335c7c522bce1b4b9b3d60f09
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 482eaa39bd9e01336086bb52ef921f811b43166095bb5268db83c0b46a8b3728d924b4e23d9c1419810dc7bcdff9d65ad4aa6a111ad6bb2c35a4148820f2ef67
|
7
|
+
data.tar.gz: f4118407512dc990b627e8fb5479de802c89601d73d9f9f6eccefa5514cf50d453995fd8a25c3bbd59519e095427949c361775e82e262e33649f46cd87d148a9
|
data/.gitignore
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
bfs_brute_force
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.1.4
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Joe Sortelli
|
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,122 @@
|
|
1
|
+
# BfsBruteForce
|
2
|
+
|
3
|
+
Lazy breadth first brute force search for solutions to puzzles.
|
4
|
+
|
5
|
+
This ruby gem provides an API for representing the initial state
|
6
|
+
and allowed next states of a puzzle, reachable through user defined
|
7
|
+
moves. The framework also provides a simple solver which will lazily
|
8
|
+
evaluate all the states in a breadth first manner to find a solution
|
9
|
+
state, returning the list of moves required to transition from the
|
10
|
+
initial state to solution state.
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
Add this line to your application's Gemfile:
|
15
|
+
|
16
|
+
gem 'bfs_brute_force'
|
17
|
+
|
18
|
+
And then execute:
|
19
|
+
|
20
|
+
$ bundle
|
21
|
+
|
22
|
+
Or install it yourself as:
|
23
|
+
|
24
|
+
$ gem install bfs_brute_force
|
25
|
+
|
26
|
+
## Usage
|
27
|
+
|
28
|
+
Your puzzle must be represented by a subclass of ```BfsBruteForce::State```.
|
29
|
+
Each instance of your State subclass must:
|
30
|
+
|
31
|
+
1. Store the current state of the puzzle (instance attributes)
|
32
|
+
2. Determine if the state is a win condition of the puzzle (```solved?```)
|
33
|
+
3. Provide a generator for reaching all possible next states (```next_states```)
|
34
|
+
|
35
|
+
### Example Puzzle
|
36
|
+
|
37
|
+
Imagine a simple puzzle where you are given a starting number, an
|
38
|
+
ending number, and you can only perform one of three addition
|
39
|
+
operations (adding one, ten, or one hundred).
|
40
|
+
|
41
|
+
To use ```BfsBruteForce``` you will create your
|
42
|
+
```BfsBruteForce::State``` subclass as follows:
|
43
|
+
require 'bfs_brute_force'
|
44
|
+
|
45
|
+
class AdditionPuzzleState < BfsBruteForce::State
|
46
|
+
attr_reader :value
|
47
|
+
|
48
|
+
def initialize(start, final)
|
49
|
+
@start = start
|
50
|
+
@value = start
|
51
|
+
@final = final
|
52
|
+
end
|
53
|
+
|
54
|
+
def solved?
|
55
|
+
@value == @final
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_s
|
59
|
+
"<#{self.class} puzzle from #{@start} to #{@final}>"
|
60
|
+
end
|
61
|
+
|
62
|
+
def next_states(already_seen)
|
63
|
+
return if @value > @final
|
64
|
+
|
65
|
+
[1, 10, 100].each do |n|
|
66
|
+
new_value = @value + n
|
67
|
+
if already_seen.add?(new_value)
|
68
|
+
yield "Add #{n}", AdditionPuzzleState.new(new_value, @final)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
Each instance of ```AdditionPuzzleState``` is immutable. The
|
75
|
+
```next_states``` method takes a single argument, which is a ```Set```
|
76
|
+
instance, that can be optionally used by your implementation to
|
77
|
+
record states that have already been evaluated, as any previously
|
78
|
+
evaluated state is already known to not be a solution.
|
79
|
+
|
80
|
+
Inside of ```next_states``` you should yield two arguments for every
|
81
|
+
valid next state of the puzzle:
|
82
|
+
|
83
|
+
1. A string, naming the move required to get to the next state
|
84
|
+
2. The next state, as a new instance of your ```BfsBruteForce::State``` class.
|
85
|
+
|
86
|
+
Now that you have your ```BfsBruteForce::State``` class, you can
|
87
|
+
initialize it with your starting puzzle state, and pass it to
|
88
|
+
```BfsBruteForce::Solver#solve```, which will return an object that
|
89
|
+
has a ```moves``` method, which returns an array of the move
|
90
|
+
names yielded by your ```next_states``` method:
|
91
|
+
|
92
|
+
solver = BfsBruteForce::Solver.new
|
93
|
+
solution = solver.solve(AdditionPuzzleState.new(0, 42))
|
94
|
+
|
95
|
+
solution.moves.each_with_index do |move, index|
|
96
|
+
puts "Move %02d) %s" % [index + 1, move]
|
97
|
+
end
|
98
|
+
|
99
|
+
## License
|
100
|
+
|
101
|
+
Copyright (c) 2014 Joe Sortelli
|
102
|
+
|
103
|
+
MIT License
|
104
|
+
|
105
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
106
|
+
a copy of this software and associated documentation files (the
|
107
|
+
"Software"), to deal in the Software without restriction, including
|
108
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
109
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
110
|
+
permit persons to whom the Software is furnished to do so, subject to
|
111
|
+
the following conditions:
|
112
|
+
|
113
|
+
The above copyright notice and this permission notice shall be
|
114
|
+
included in all copies or substantial portions of the Software.
|
115
|
+
|
116
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
117
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
118
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
119
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
120
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
121
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
122
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'bfs_brute_force/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "bfs_brute_force"
|
8
|
+
spec.version = BfsBruteForce::VERSION
|
9
|
+
spec.authors = ["Joe Sortelli"]
|
10
|
+
spec.email = ["joe@sortelli.com"]
|
11
|
+
spec.summary = "Lazy breadth first brute force search for solutions to puzzles"
|
12
|
+
spec.description = %q{
|
13
|
+
Provides an API for representing the initial state and allowed
|
14
|
+
next states of a puzzle, reachable through user defined moves.
|
15
|
+
The framework also provides a simple solver which will lazily
|
16
|
+
evaluate all the states in a breadth first manner to find a
|
17
|
+
solution state, returning the list of moves required to transition
|
18
|
+
from the initial state to solution state.
|
19
|
+
}
|
20
|
+
spec.homepage = "https://github.com/sortelli/bfs_brute_force"
|
21
|
+
spec.license = "MIT"
|
22
|
+
|
23
|
+
spec.files = `git ls-files -z`.split("\x0")
|
24
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
25
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
26
|
+
spec.require_paths = ["lib"]
|
27
|
+
|
28
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
29
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
30
|
+
spec.add_development_dependency "minitest", "~> 4.7"
|
31
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require "bfs_brute_force/version"
|
2
|
+
require "set"
|
3
|
+
|
4
|
+
# Top level module for this framework
|
5
|
+
module BfsBruteForce
|
6
|
+
# Exception thrown by {Solver#solve}
|
7
|
+
class NoSolution < StandardError
|
8
|
+
# @param num_of_solutions_tried [Fixnum] number of solutions previously tried
|
9
|
+
def initialize(num_of_solutions_tried)
|
10
|
+
super("No solution in #{num_of_solutions_tried} tries. There are no more states to analyze")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Context object that contains a State and a list of moves required
|
15
|
+
# to reach that State from the initial State.
|
16
|
+
#
|
17
|
+
# @!attribute state [r]
|
18
|
+
# @return [State] the current state
|
19
|
+
#
|
20
|
+
# @!attribute moves [r]
|
21
|
+
# @return [Array] the list of moves to this state from
|
22
|
+
# the initial state
|
23
|
+
class Context
|
24
|
+
attr_reader :state, :moves
|
25
|
+
|
26
|
+
# @param state [State] current state
|
27
|
+
# @param already_seen [Set] set of states already processed
|
28
|
+
# @param moves [Array] list of moves to get to this state
|
29
|
+
def initialize(state, already_seen = Set.new, moves = [])
|
30
|
+
@state = state
|
31
|
+
@already_seen = already_seen
|
32
|
+
@moves = moves
|
33
|
+
end
|
34
|
+
|
35
|
+
# Check if current state is a solution
|
36
|
+
# @return [Boolean]
|
37
|
+
def solved?
|
38
|
+
@state.solved?
|
39
|
+
end
|
40
|
+
|
41
|
+
# Generate all contexts that can be reached from this current context
|
42
|
+
# @return [void]
|
43
|
+
# @yieldparam next_context [Context] next context
|
44
|
+
def next_contexts
|
45
|
+
@state.next_states(@already_seen) do |next_move, next_state|
|
46
|
+
yield Context.new(next_state, @already_seen, @moves + [next_move])
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Single state in a puzzle. Represent your puzzle as
|
52
|
+
# a subclass of {State}.
|
53
|
+
#
|
54
|
+
# @abstract Override {#next_states}, {#to_s} and {#solved?}
|
55
|
+
class State
|
56
|
+
# Your implementation should yield a (move,state) pair for every
|
57
|
+
# state reachable by the current state.
|
58
|
+
#
|
59
|
+
# You should make use of the already_seen set to only yield
|
60
|
+
# states that have not previously been yielded.
|
61
|
+
#
|
62
|
+
# @example Use already_seen to only yield states not already yielded
|
63
|
+
# def next_states(already_seen)
|
64
|
+
# next_value = @my_value + 100
|
65
|
+
#
|
66
|
+
# # See {Set#add?}. Returns nil value is already in the Set.
|
67
|
+
# if already_seen.add?(next_value)
|
68
|
+
# yield "Add 100", MyState.new(next_value)
|
69
|
+
# end
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
# @param already_seen [Set] Set of all already processed states
|
73
|
+
#
|
74
|
+
# @yield [move, state]
|
75
|
+
# @yieldparam move [#to_s] Text description of a state transition
|
76
|
+
# @yieldparam state [State] New state, reachable from current state with
|
77
|
+
# the provided move
|
78
|
+
#
|
79
|
+
# @raise [NotImplementedError] if you failed to provide your own implementation
|
80
|
+
# @return [void]
|
81
|
+
def next_states(already_seen)
|
82
|
+
raise NotImplementedError, "next_states is not implemented yet"
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns true if current state is a solution to the puzzle.
|
86
|
+
#
|
87
|
+
# @raise [NotImplementedError] if you failed to provide your own implementation
|
88
|
+
# @return [Boolean]
|
89
|
+
def solved?
|
90
|
+
raise NotImplementedError, "solved? is not implemented yet"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Lazy breadth first puzzle solver
|
95
|
+
class Solver
|
96
|
+
# Find a list of moves from the starting state of the puzzle to a solution state.
|
97
|
+
#
|
98
|
+
# @param initial_state [State] Initial state of your puzzle
|
99
|
+
# @param status [#<<] IO object to receive status messages
|
100
|
+
#
|
101
|
+
# @raise [NoSolution] No solution is found
|
102
|
+
# @return [Context] Solved Context object has the final {State} and list of moves
|
103
|
+
def solve(initial_state, status = $stdout)
|
104
|
+
status << "Looking for solution for:\n#{initial_state}\n\n"
|
105
|
+
|
106
|
+
initial_context = Context.new(initial_state)
|
107
|
+
|
108
|
+
if initial_context.solved?
|
109
|
+
status << "Good news, its already solved\n"
|
110
|
+
return initial_context
|
111
|
+
end
|
112
|
+
|
113
|
+
tries = 0
|
114
|
+
contexts = [initial_context]
|
115
|
+
|
116
|
+
until contexts.empty?
|
117
|
+
status << ("Checking for solutions that take %4d moves ... " % [
|
118
|
+
contexts.first.moves.size + 1
|
119
|
+
])
|
120
|
+
|
121
|
+
new_contexts = []
|
122
|
+
|
123
|
+
contexts.each do |current_context|
|
124
|
+
current_context.next_contexts do |context|
|
125
|
+
tries += 1
|
126
|
+
|
127
|
+
if context.solved?
|
128
|
+
status << "solved in #{tries} tries\n\nMoves:\n"
|
129
|
+
context.moves.each {|m| status << " #{m}\n"}
|
130
|
+
status << "\nFinal state:\n #{context.state}\n"
|
131
|
+
return context
|
132
|
+
end
|
133
|
+
|
134
|
+
new_contexts << context
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
contexts = new_contexts
|
139
|
+
status << ("none in %9d new states\n" % contexts.size)
|
140
|
+
end
|
141
|
+
|
142
|
+
raise NoSolution.new(tries)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
data/test/basic.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require "minitest/autorun"
|
2
|
+
require "bfs_brute_force"
|
3
|
+
|
4
|
+
class AlreadySolvedState < BfsBruteForce::State
|
5
|
+
def solved?
|
6
|
+
true
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class BrokenState < BfsBruteForce::State
|
11
|
+
def solved?
|
12
|
+
false
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class TestBasic < Minitest::Unit::TestCase
|
17
|
+
def test_module_exists
|
18
|
+
mod_key = :BfsBruteForce
|
19
|
+
assert Kernel.const_defined?(mod_key), "Module #{mod_key} missing"
|
20
|
+
|
21
|
+
mod = Kernel.const_get mod_key
|
22
|
+
%w{Context State Solver}.each do |c|
|
23
|
+
assert mod.const_defined?(c), "Class #{mod}::#{c} missing"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_already_solved
|
28
|
+
state = AlreadySolvedState.new
|
29
|
+
solver = BfsBruteForce::Solver.new
|
30
|
+
|
31
|
+
assert_raises(NotImplementedError) {state.next_states(nil)}
|
32
|
+
assert state.solved?
|
33
|
+
|
34
|
+
solver.solve state, []
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_broken
|
38
|
+
state = BrokenState.new
|
39
|
+
solver = BfsBruteForce::Solver.new
|
40
|
+
|
41
|
+
assert_raises(NotImplementedError) {state.next_states(nil)}
|
42
|
+
refute state.solved?
|
43
|
+
|
44
|
+
assert_raises(NotImplementedError) { solver.solve(state, []) }
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require "minitest/autorun"
|
2
|
+
require "bfs_brute_force"
|
3
|
+
|
4
|
+
class SimplePuzzleState < BfsBruteForce::State
|
5
|
+
attr_reader :value
|
6
|
+
|
7
|
+
def initialize(start, final)
|
8
|
+
@start = start
|
9
|
+
@value = start
|
10
|
+
@final = final
|
11
|
+
end
|
12
|
+
|
13
|
+
def solved?
|
14
|
+
@value == @final
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
"<#{self.class} puzzle from #{@start} to #{@final}>"
|
19
|
+
end
|
20
|
+
|
21
|
+
def next_states(already_seen)
|
22
|
+
return if @value > @final
|
23
|
+
|
24
|
+
[1, 10, 100].each do |n|
|
25
|
+
new_value = @value + n
|
26
|
+
if already_seen.add?(new_value)
|
27
|
+
yield "Add #{n}", SimplePuzzleState.new(new_value, @final)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class SlightlyHarderPuzzleState < SimplePuzzleState
|
34
|
+
def next_states(already_seen)
|
35
|
+
[10, 100].each do |n|
|
36
|
+
new_value = @value + n
|
37
|
+
if already_seen.add?(new_value)
|
38
|
+
yield "Add #{n}", SlightlyHarderPuzzleState.new(new_value, @final)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
new_value = @value - 1
|
43
|
+
if already_seen.add?(new_value)
|
44
|
+
yield "Subtract 1", SlightlyHarderPuzzleState.new(new_value, @final)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class TestSimplePuzzle < Minitest::Unit::TestCase
|
50
|
+
def test_simple_puzzle
|
51
|
+
[
|
52
|
+
[0, 42, ["Add 1"] * 2 + ["Add 10"] * 4],
|
53
|
+
[2, 42, ["Add 10"] * 4],
|
54
|
+
[3, 427, ["Add 1"] * 4 + ["Add 10"] * 2 + ["Add 100"] * 4]
|
55
|
+
].each do |args|
|
56
|
+
solve_puzzle(SimplePuzzleState, *args)
|
57
|
+
end
|
58
|
+
|
59
|
+
assert_raises(BfsBruteForce::NoSolution) do
|
60
|
+
solve_puzzle(SimplePuzzleState, 3, 2, [])
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_slightly_harder_puzzle
|
65
|
+
[
|
66
|
+
[0, 42, ["Add 10"] * 5 + ["Subtract 1"] * 8],
|
67
|
+
[2, 42, ["Add 10"] * 4],
|
68
|
+
[3, 427, ["Add 10"] * 3 + ["Add 100"] * 4 + ["Subtract 1"] * 6]
|
69
|
+
].each do |args|
|
70
|
+
solve_puzzle(SlightlyHarderPuzzleState, *args)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def solve_puzzle(type, start, final, expected_moves)
|
75
|
+
state = type.new(start, final)
|
76
|
+
solver = BfsBruteForce::Solver.new
|
77
|
+
|
78
|
+
refute state.solved?, "Not already solved"
|
79
|
+
|
80
|
+
context = solver.solve state
|
81
|
+
|
82
|
+
assert_instance_of(BfsBruteForce::Context, context)
|
83
|
+
assert_instance_of(type, context.state)
|
84
|
+
assert_instance_of(Array, context.moves)
|
85
|
+
|
86
|
+
assert context.solved?
|
87
|
+
assert context.state.solved?
|
88
|
+
assert_equal context.state.value, final
|
89
|
+
assert_equal expected_moves, context.moves
|
90
|
+
end
|
91
|
+
end
|
metadata
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bfs_brute_force
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Joe Sortelli
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-12-13 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.7'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
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: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '4.7'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '4.7'
|
55
|
+
description: "\n Provides an API for representing the initial state and allowed\n
|
56
|
+
\ next states of a puzzle, reachable through user defined moves.\n The framework
|
57
|
+
also provides a simple solver which will lazily\n evaluate all the states in
|
58
|
+
a breadth first manner to find a\n solution state, returning the list of moves
|
59
|
+
required to transition\n from the initial state to solution state.\n "
|
60
|
+
email:
|
61
|
+
- joe@sortelli.com
|
62
|
+
executables: []
|
63
|
+
extensions: []
|
64
|
+
extra_rdoc_files: []
|
65
|
+
files:
|
66
|
+
- ".gitignore"
|
67
|
+
- ".ruby-gemset"
|
68
|
+
- ".ruby-version"
|
69
|
+
- Gemfile
|
70
|
+
- LICENSE.txt
|
71
|
+
- README.md
|
72
|
+
- Rakefile
|
73
|
+
- bfs_brute_force.gemspec
|
74
|
+
- lib/bfs_brute_force.rb
|
75
|
+
- lib/bfs_brute_force/version.rb
|
76
|
+
- test/basic.rb
|
77
|
+
- test/simple_puzzle.rb
|
78
|
+
homepage: https://github.com/sortelli/bfs_brute_force
|
79
|
+
licenses:
|
80
|
+
- MIT
|
81
|
+
metadata: {}
|
82
|
+
post_install_message:
|
83
|
+
rdoc_options: []
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
requirements: []
|
97
|
+
rubyforge_project:
|
98
|
+
rubygems_version: 2.4.4
|
99
|
+
signing_key:
|
100
|
+
specification_version: 4
|
101
|
+
summary: Lazy breadth first brute force search for solutions to puzzles
|
102
|
+
test_files:
|
103
|
+
- test/basic.rb
|
104
|
+
- test/simple_puzzle.rb
|