knossos 0.2.0 → 0.3.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 +4 -4
- data/CHANGELOG.md +51 -11
- data/README.md +4 -3
- data/lib/knossos.rb +2 -0
- data/lib/knossos/algorithm.rb +1 -0
- data/lib/knossos/algorithm/aldous_broder.rb +7 -1
- data/lib/knossos/algorithm/binary_tree.rb +17 -7
- data/lib/knossos/algorithm/hunt_and_kill.rb +49 -0
- data/lib/knossos/algorithm/recursive_backtracker.rb +9 -1
- data/lib/knossos/algorithm/sidewinder.rb +16 -12
- data/lib/knossos/algorithm/wilsons.rb +7 -1
- data/lib/knossos/renderer/text.rb +9 -2
- data/lib/knossos/serializer.rb +6 -0
- data/lib/knossos/serializer/bitmask.rb +65 -0
- data/lib/knossos/solver.rb +7 -0
- data/lib/knossos/solver/dijkstra.rb +61 -0
- data/lib/knossos/solver/distances.rb +37 -0
- data/lib/knossos/version.rb +1 -1
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8c2bf5993f7288ba9f26d3d51455bf2844f36bd650e1cc3816eaac6e181c6316
|
4
|
+
data.tar.gz: fd09305e92ecf3ff5332b251888659187258ba3b4e6d59e36df64741e7c7c471
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 14ae5ceee9e2771b005cbb7900cc753ac7279e3083cc2eb802c96372068d98bc4aea057561afc85a6daca227b45d794f128b1234688d9580ea056d38a82df5db
|
7
|
+
data.tar.gz: 135dddf5a3c9542cf346f618c14e27444fae24a58669523549140077703176dc60009159de8b23af4393a0ae996e2e7e27b669afd3cc3e30c09d61822a79470a
|
data/CHANGELOG.md
CHANGED
@@ -4,32 +4,70 @@ All notable changes to this project will be documented in this file.
|
|
4
4
|
This project uses [Semantic Versioning][sv].
|
5
5
|
|
6
6
|
## [Unreleased][new]
|
7
|
+
|
8
|
+
## [0.3.0][0.3.0] — 2020-01-26
|
9
|
+
|
10
|
+
### Added
|
11
|
+
- Add the `HuntAndKill` algorithm for generating mazes.
|
12
|
+
- `BinaryTree` accepts an option to configure the bias.
|
13
|
+
- Algorithms accept an argument to `#carve` to set the seed for the random number
|
14
|
+
generator.
|
15
|
+
- Create a `Serializer` module to hold the methods that encode mazes for
|
16
|
+
archiving and transfer.
|
17
|
+
- Add the `Bitmask` serializer to represent the maze as a matrix of integers,
|
18
|
+
where each integer is a bitfield encoding the passage directions.
|
19
|
+
- Add `Bitmask#deserialize` to decode an array of bitmasked integers into a
|
20
|
+
maze.
|
21
|
+
- Create a `Solver` module to hold algorithms that analyze or plot solutions to
|
22
|
+
mazes.
|
23
|
+
- Create `Solver::Distances` helper class to store the distances from a root
|
24
|
+
cell to any other cell in the maze.
|
25
|
+
- Add `Dijkstra#distances_from` to calculate the distances from a given cell to
|
26
|
+
every other cell in the maze.
|
27
|
+
- Add `Dijkstra#shortest_path` to extract the shortest path from the starting
|
28
|
+
cell to a given goal.
|
29
|
+
- Add `Dijkstra#longest_path` to find the longest path in the entire maze.
|
30
|
+
- Display all distances or the solution path within the rendered grid.
|
31
|
+
|
32
|
+
### Changed
|
33
|
+
- Change `carve` to be an instance method of the Algorithm classes.
|
34
|
+
|
35
|
+
### Fixed
|
36
|
+
- Update Usage in README with namespace changes.
|
37
|
+
|
38
|
+
## [0.2.0][0.2.0] — 2020-01-09
|
39
|
+
|
7
40
|
### Changed
|
8
41
|
- Correctly nest the `Algorithm` module within the `Knossos` namespace.
|
9
42
|
- Correctly nest the `Renderer` module within the `Knossos` namespace.
|
10
43
|
|
11
44
|
## [0.1.0][0.1.0] — 2020-01-07
|
45
|
+
|
12
46
|
### Added
|
13
47
|
- Create an `Algorithm` module to hold the maze generators.
|
14
48
|
- Create a `Renderer` module to hold the methods that produce graphical
|
15
49
|
representations of a maze.
|
16
50
|
- Create `Renderer::Text` to produce ASCII text representations.
|
51
|
+
|
17
52
|
### Changed
|
18
|
-
- Move `AldousBroder` to the `Algorithm` module
|
19
|
-
- Move `BinaryTree` to the `Algorithm` module
|
20
|
-
- Move `RecursiveBacktracker` to the `Algorithm` module
|
21
|
-
- Move `Sidewinder` to the `Algorithm` module
|
22
|
-
- Move `Wilsons` to the `Algorithm` module
|
23
|
-
- Move and rename `Display` to `Renderer::Image
|
24
|
-
- Rename `Display.to_png` to `Image.render
|
53
|
+
- Move `AldousBroder` to the `Algorithm` module.
|
54
|
+
- Move `BinaryTree` to the `Algorithm` module.
|
55
|
+
- Move `RecursiveBacktracker` to the `Algorithm` module.
|
56
|
+
- Move `Sidewinder` to the `Algorithm` module.
|
57
|
+
- Move `Wilsons` to the `Algorithm` module.
|
58
|
+
- Move and rename `Display` to `Renderer::Image`.
|
59
|
+
- Rename `Display.to_png` to `Image.render`.
|
60
|
+
|
25
61
|
### Fixed
|
26
|
-
- Correctly scope private class methods in `Sidewinder
|
27
|
-
- Algorithms now uniformly require a `grid:` keyword argument to `.carve
|
62
|
+
- Correctly scope private class methods in `Sidewinder`.
|
63
|
+
- Algorithms now uniformly require a `grid:` keyword argument to `.carve`.
|
64
|
+
|
28
65
|
### Removed
|
29
66
|
- `Grid` no longer has a custom `to_s` method to produce a graphical maze.
|
30
|
-
Use `Renderer::Text.render` instead.
|
67
|
+
Use `Renderer::Text.render` instead.
|
31
68
|
|
32
69
|
## [0.0.0][0.0.0] — 2020-01-02
|
70
|
+
|
33
71
|
### Added
|
34
72
|
- Restructure the project as a Ruby gem.
|
35
73
|
|
@@ -39,6 +77,8 @@ _This file is composed with [GitHub Flavored Markdown][gfm]._
|
|
39
77
|
[gfm]: https://github.github.com/gfm/
|
40
78
|
[sv]: https://semver.org
|
41
79
|
|
42
|
-
[new]: https://github.com/petejh/knossos/compare/HEAD..v0.
|
80
|
+
[new]: https://github.com/petejh/knossos/compare/HEAD..v0.3.0
|
81
|
+
[0.3.0]: https://github.com/petejh/knossos/releases/tag/v0.3.0
|
82
|
+
[0.2.0]: https://github.com/petejh/knossos/releases/tag/v0.2.0
|
43
83
|
[0.1.0]: https://github.com/petejh/knossos/releases/tag/v0.1.0
|
44
84
|
[0.0.0]: https://github.com/petejh/knossos/releases/tag/v0.0.0
|
data/README.md
CHANGED
@@ -19,12 +19,13 @@ Or install the library globally with:
|
|
19
19
|
require 'knossos'
|
20
20
|
|
21
21
|
grid = Knossos::Grid.new({rows: 5, columns: 5})
|
22
|
-
Algorithm::BinaryTree.
|
22
|
+
carver = Knossos::Algorithm::BinaryTree.new
|
23
|
+
carver.carve(grid: grid)
|
23
24
|
|
24
|
-
text_renderer = Renderer::Text.new
|
25
|
+
text_renderer = Knossos::Renderer::Text.new
|
25
26
|
puts text_renderer.render(grid: grid)
|
26
27
|
|
27
|
-
image_renderer = Renderer::Image.new(grid)
|
28
|
+
image_renderer = Knossos::Renderer::Image.new(grid)
|
28
29
|
png = image_renderer.render
|
29
30
|
png.save("maze.png")
|
30
31
|
```
|
data/lib/knossos.rb
CHANGED
data/lib/knossos/algorithm.rb
CHANGED
@@ -1,15 +1,19 @@
|
|
1
1
|
module Knossos
|
2
2
|
module Algorithm
|
3
3
|
class BinaryTree
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
attr_reader :bias
|
5
|
+
|
6
|
+
def initialize(**options)
|
7
|
+
options = defaults.merge(options)
|
8
|
+
|
9
|
+
@bias = options[:bias]
|
10
|
+
end
|
7
11
|
|
8
|
-
|
9
|
-
|
12
|
+
def carve(grid:, seed: nil)
|
13
|
+
srand(seed || Kernel.srand)
|
10
14
|
|
11
|
-
|
12
|
-
neighbors
|
15
|
+
grid.each_cell do |cell|
|
16
|
+
neighbors = bias.map { |direction| grid.send(direction, cell) }.compact
|
13
17
|
|
14
18
|
neighbor = neighbors.sample
|
15
19
|
grid.build_passage(cell, neighbor) if neighbor
|
@@ -17,6 +21,12 @@ module Knossos
|
|
17
21
|
|
18
22
|
grid
|
19
23
|
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def defaults
|
28
|
+
{ bias: [:north, :east] }
|
29
|
+
end
|
20
30
|
end
|
21
31
|
end
|
22
32
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Knossos
|
2
|
+
module Algorithm
|
3
|
+
class HuntAndKill
|
4
|
+
attr_reader :bias
|
5
|
+
|
6
|
+
def initialize(**options)
|
7
|
+
options = defaults.merge(options)
|
8
|
+
end
|
9
|
+
|
10
|
+
def carve(grid:, seed: nil)
|
11
|
+
srand(seed || Kernel.srand)
|
12
|
+
|
13
|
+
current = grid.random_cell
|
14
|
+
|
15
|
+
while current
|
16
|
+
unvisited_neighbors = grid.neighborhood(current).select { |n| n.links.empty? }
|
17
|
+
|
18
|
+
if unvisited_neighbors.any?
|
19
|
+
neighbor = unvisited_neighbors.sample
|
20
|
+
grid.build_passage(current, neighbor)
|
21
|
+
current = neighbor
|
22
|
+
else
|
23
|
+
current = nil
|
24
|
+
|
25
|
+
grid.each_cell do |cell|
|
26
|
+
visited_neighbors = grid.neighborhood(cell).select { |n| n.links.any? }
|
27
|
+
if cell.links.empty? && visited_neighbors.any?
|
28
|
+
current = cell
|
29
|
+
|
30
|
+
neighbor = visited_neighbors.sample
|
31
|
+
grid.build_passage(current, neighbor)
|
32
|
+
|
33
|
+
break
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
grid
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def defaults
|
45
|
+
{}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -1,7 +1,15 @@
|
|
1
1
|
module Knossos
|
2
2
|
module Algorithm
|
3
3
|
class RecursiveBacktracker
|
4
|
-
def
|
4
|
+
def initialize
|
5
|
+
# nothing to do
|
6
|
+
end
|
7
|
+
|
8
|
+
def carve(grid:, start_at: nil, seed: nil)
|
9
|
+
srand(seed || Kernel.srand)
|
10
|
+
|
11
|
+
start_at ||= grid.random_cell
|
12
|
+
|
5
13
|
stack = []
|
6
14
|
stack.push start_at
|
7
15
|
|
@@ -1,7 +1,13 @@
|
|
1
1
|
module Knossos
|
2
2
|
module Algorithm
|
3
3
|
class Sidewinder
|
4
|
-
def
|
4
|
+
def initialize
|
5
|
+
# nothing to do
|
6
|
+
end
|
7
|
+
|
8
|
+
def carve(grid:, seed: nil)
|
9
|
+
srand(seed || Kernel.srand)
|
10
|
+
|
5
11
|
grid.each_row do |row|
|
6
12
|
run = []
|
7
13
|
row.each do |cell|
|
@@ -24,20 +30,18 @@ module Knossos
|
|
24
30
|
grid
|
25
31
|
end
|
26
32
|
|
27
|
-
|
28
|
-
private
|
33
|
+
private
|
29
34
|
|
30
|
-
|
31
|
-
|
32
|
-
|
35
|
+
def close_out_run?(grid, cell)
|
36
|
+
at_east_border?(grid, cell) || (!at_north_border?(grid, cell) && rand(2) == 0)
|
37
|
+
end
|
33
38
|
|
34
|
-
|
35
|
-
|
36
|
-
|
39
|
+
def at_east_border?(grid, cell)
|
40
|
+
grid.east(cell).nil?
|
41
|
+
end
|
37
42
|
|
38
|
-
|
39
|
-
|
40
|
-
end
|
43
|
+
def at_north_border?(grid, cell)
|
44
|
+
grid.north(cell).nil?
|
41
45
|
end
|
42
46
|
end
|
43
47
|
end
|
@@ -5,7 +5,7 @@ module Knossos
|
|
5
5
|
# nothing to do
|
6
6
|
end
|
7
7
|
|
8
|
-
def render(grid:)
|
8
|
+
def render(grid:, distances: nil)
|
9
9
|
output = "+" + "---+" * grid.columns + "\n"
|
10
10
|
|
11
11
|
grid.each_row do |row|
|
@@ -19,7 +19,7 @@ module Knossos
|
|
19
19
|
south = grid.south(cell)
|
20
20
|
south_border = cell.linked?(south) ? " " : "---"
|
21
21
|
|
22
|
-
body <<
|
22
|
+
body << decorate(cell, distances) << east_border
|
23
23
|
bottom << south_border << "+"
|
24
24
|
end
|
25
25
|
|
@@ -29,6 +29,13 @@ module Knossos
|
|
29
29
|
|
30
30
|
output
|
31
31
|
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def decorate(cell, distances)
|
36
|
+
label = distances && distances[cell] ? distances[cell].to_s(36) : ' '
|
37
|
+
" #{label} "
|
38
|
+
end
|
32
39
|
end
|
33
40
|
end
|
34
41
|
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Knossos
|
2
|
+
module Serializer
|
3
|
+
class Bitmask
|
4
|
+
NORTH = 1
|
5
|
+
EAST = 2
|
6
|
+
SOUTH = 4
|
7
|
+
WEST = 8
|
8
|
+
|
9
|
+
def serialize(grid:)
|
10
|
+
encoded_maze = Array.new(grid.columns) { Array.new(grid.rows, 0) }
|
11
|
+
|
12
|
+
grid.each_row do |cells, row|
|
13
|
+
cells.each_with_index do |cell, column|
|
14
|
+
link_mask = 0
|
15
|
+
|
16
|
+
cell.links.each do |link|
|
17
|
+
link_mask |=
|
18
|
+
case column <=> link.column
|
19
|
+
when -1
|
20
|
+
EAST
|
21
|
+
when 0
|
22
|
+
0
|
23
|
+
when 1
|
24
|
+
WEST
|
25
|
+
end
|
26
|
+
|
27
|
+
link_mask |=
|
28
|
+
case row <=> link.row
|
29
|
+
when -1
|
30
|
+
SOUTH
|
31
|
+
when 0
|
32
|
+
0
|
33
|
+
when 1
|
34
|
+
NORTH
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
encoded_maze[row][column] = link_mask
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
encoded_maze
|
43
|
+
end
|
44
|
+
|
45
|
+
def deserialize(encoded_maze:)
|
46
|
+
rows = encoded_maze.length
|
47
|
+
columns = encoded_maze.first.length
|
48
|
+
grid = Knossos::Grid.new({rows: rows, columns: columns})
|
49
|
+
|
50
|
+
encoded_maze.each_with_index do |cells, row|
|
51
|
+
cells.each_with_index do |cell, column|
|
52
|
+
current_cell = grid[row, column]
|
53
|
+
|
54
|
+
grid.build_passage(current_cell, grid[row - 1, column]) if (cell & NORTH > 0)
|
55
|
+
grid.build_passage(current_cell, grid[row, column + 1]) if (cell & EAST > 0)
|
56
|
+
grid.build_passage(current_cell, grid[row + 1, column]) if (cell & SOUTH > 0)
|
57
|
+
grid.build_passage(current_cell, grid[row, column - 1]) if (cell & WEST > 0)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
grid
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Knossos
|
2
|
+
module Solver
|
3
|
+
class Dijkstra
|
4
|
+
def initialize
|
5
|
+
# nothing to do
|
6
|
+
end
|
7
|
+
|
8
|
+
def distances_from(start:)
|
9
|
+
frontier = [start]
|
10
|
+
distances = Distances.new(root: start)
|
11
|
+
|
12
|
+
while frontier.any?
|
13
|
+
new_frontier = []
|
14
|
+
|
15
|
+
frontier.each do |cell|
|
16
|
+
cell.links.each do |linked_cell|
|
17
|
+
next if distances[linked_cell]
|
18
|
+
distances[linked_cell] = distances[cell] + 1
|
19
|
+
new_frontier << linked_cell
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
frontier = new_frontier
|
24
|
+
end
|
25
|
+
|
26
|
+
distances
|
27
|
+
end
|
28
|
+
|
29
|
+
def shortest_path(start:, goal:, distances:)
|
30
|
+
current = goal
|
31
|
+
|
32
|
+
path = Distances.new(root: start)
|
33
|
+
path[current] = distances[current]
|
34
|
+
|
35
|
+
until current == start
|
36
|
+
current.links.each do |neighbor|
|
37
|
+
if distances[neighbor] < distances[current]
|
38
|
+
path[neighbor] = distances[neighbor]
|
39
|
+
current = neighbor
|
40
|
+
break
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
path
|
46
|
+
end
|
47
|
+
|
48
|
+
def longest_path(distances:)
|
49
|
+
# Find the cell that is furthest from any given starting cell. Then find
|
50
|
+
# the cell that is furthest from that one. This is guaranteed to yield
|
51
|
+
# the longest path in a perfect maze.
|
52
|
+
|
53
|
+
new_start, _ = distances.max
|
54
|
+
new_distances = distances_from(start: new_start)
|
55
|
+
goal, _ = new_distances.max
|
56
|
+
|
57
|
+
shortest_path(start: new_start, goal: goal, distances: new_distances)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Knossos
|
2
|
+
module Solver
|
3
|
+
class Distances
|
4
|
+
def initialize(root:)
|
5
|
+
@root = root
|
6
|
+
@cells = {}
|
7
|
+
@cells[@root] = 0
|
8
|
+
end
|
9
|
+
|
10
|
+
def [](cell)
|
11
|
+
@cells[cell]
|
12
|
+
end
|
13
|
+
|
14
|
+
def []=(cell, distance)
|
15
|
+
@cells[cell] = distance
|
16
|
+
end
|
17
|
+
|
18
|
+
def cells
|
19
|
+
@cells.keys
|
20
|
+
end
|
21
|
+
|
22
|
+
def max
|
23
|
+
max_distance = 0
|
24
|
+
max_cell = @root
|
25
|
+
|
26
|
+
@cells.each do |cell, distance|
|
27
|
+
if distance > max_distance
|
28
|
+
max_cell = cell
|
29
|
+
max_distance = distance
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
[max_cell, max_distance]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/knossos/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: knossos
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Peter J. Hinckley
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-01-
|
11
|
+
date: 2020-01-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: chunky_png
|
@@ -81,6 +81,7 @@ files:
|
|
81
81
|
- lib/knossos/algorithm.rb
|
82
82
|
- lib/knossos/algorithm/aldous_broder.rb
|
83
83
|
- lib/knossos/algorithm/binary_tree.rb
|
84
|
+
- lib/knossos/algorithm/hunt_and_kill.rb
|
84
85
|
- lib/knossos/algorithm/recursive_backtracker.rb
|
85
86
|
- lib/knossos/algorithm/sidewinder.rb
|
86
87
|
- lib/knossos/algorithm/wilsons.rb
|
@@ -91,6 +92,11 @@ files:
|
|
91
92
|
- lib/knossos/renderer/image.rb
|
92
93
|
- lib/knossos/renderer/png_adapter.rb
|
93
94
|
- lib/knossos/renderer/text.rb
|
95
|
+
- lib/knossos/serializer.rb
|
96
|
+
- lib/knossos/serializer/bitmask.rb
|
97
|
+
- lib/knossos/solver.rb
|
98
|
+
- lib/knossos/solver/dijkstra.rb
|
99
|
+
- lib/knossos/solver/distances.rb
|
94
100
|
- lib/knossos/version.rb
|
95
101
|
homepage: https://github.com/petejh/knossos
|
96
102
|
licenses:
|