hanoi-jane 0.2.5 → 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/README.md +12 -12
- data/TODO.md +1 -0
- data/config/config.yml +30 -0
- data/lib/hanoi/jane.rb +23 -5
- data/lib/hanoi/jane/animation/animation.rb +35 -0
- data/lib/hanoi/jane/animation/dropper.rb +36 -0
- data/lib/hanoi/jane/animation/lifter.rb +46 -0
- data/lib/hanoi/jane/animation/padded_stacks.rb +15 -0
- data/lib/hanoi/jane/cli.rb +32 -47
- data/lib/hanoi/jane/config.rb +25 -0
- data/lib/hanoi/jane/formatters/console.rb +48 -50
- data/lib/hanoi/jane/formatters/matrix.rb +74 -50
- data/lib/hanoi/jane/towers/animated_towers.rb +60 -0
- data/lib/hanoi/jane/towers/constrained_towers.rb +22 -0
- data/lib/hanoi/jane/towers/stack_finders.rb +36 -0
- data/lib/hanoi/jane/towers/towers.rb +114 -0
- data/lib/hanoi/jane/version.rb +1 -1
- metadata +13 -5
- data/lib/hanoi/jane/animation.rb +0 -79
- data/lib/hanoi/jane/constrained_towers.rb +0 -36
- data/lib/hanoi/jane/towers.rb +0 -122
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c8a1d92da6a843d6068462542eb6728d80995eed
|
4
|
+
data.tar.gz: dbac7d2f1d65726fc56aad01a1653f8818915e22
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 49ed23e2f5f6abe0f6585cee6e023098ddbff0cbf70ad90c09fe499f2483264a3cce98d2046dc8324a6e219fdbe7b71931f6800856d1877f2864585a60f46e09
|
7
|
+
data.tar.gz: e2b44701f251b4c172c8f01880878c8f61a789b75e3d1526e1f006846a05c0dcafd4bd22f99af214f1f3de8e8d5e0768f31b24b330e1d81b96d5ce2c09463561
|
data/README.md
CHANGED
@@ -45,15 +45,15 @@ end
|
|
45
45
|
which will give you:
|
46
46
|
|
47
47
|
```ruby
|
48
|
-
{:stacks=>[[1, 0], [], []], :moves=>0, :moved=>nil, :ternary=>
|
49
|
-
{:stacks=>[[1], [0], []], :moves=>1, :moved=>0, :ternary=>
|
50
|
-
{:stacks=>[[1], [], [0]], :moves=>2, :moved=>0, :ternary=>
|
51
|
-
{:stacks=>[[], [1], [0]], :moves=>3, :moved=>1, :ternary=>
|
52
|
-
{:stacks=>[[], [1, 0], []], :moves=>4, :moved=>0, :ternary=>
|
53
|
-
{:stacks=>[[0], [1], []], :moves=>5, :moved=>0, :ternary=>
|
54
|
-
{:stacks=>[[0], [], [1]], :moves=>6, :moved=>1, :ternary=>
|
55
|
-
{:stacks=>[[], [0], [1]], :moves=>7, :moved=>0, :ternary=>
|
56
|
-
{:stacks=>[[], [], [1, 0]], :moves=>8, :moved=>0, :ternary=>
|
48
|
+
{:stacks=>[[1, 0], [], []], :moves=>0, :moved=>{:disc=>nil, :from=>nil, :to=>nil}, :ternary=>"00"}
|
49
|
+
{:stacks=>[[1], [0], []], :moves=>1, :moved=>{:disc=>0, :from=>0, :to=>1}, :ternary=>"01"}
|
50
|
+
{:stacks=>[[1], [], [0]], :moves=>2, :moved=>{:disc=>0, :from=>1, :to=>2}, :ternary=>"02"}
|
51
|
+
{:stacks=>[[], [1], [0]], :moves=>3, :moved=>{:disc=>1, :from=>0, :to=>1}, :ternary=>"10"}
|
52
|
+
{:stacks=>[[], [1, 0], []], :moves=>4, :moved=>{:disc=>0, :from=>2, :to=>1}, :ternary=>"11"}
|
53
|
+
{:stacks=>[[0], [1], []], :moves=>5, :moved=>{:disc=>0, :from=>1, :to=>0}, :ternary=>"12"}
|
54
|
+
{:stacks=>[[0], [], [1]], :moves=>6, :moved=>{:disc=>1, :from=>1, :to=>2}, :ternary=>"20"}
|
55
|
+
{:stacks=>[[], [0], [1]], :moves=>7, :moved=>{:disc=>0, :from=>0, :to=>1}, :ternary=>"21"}
|
56
|
+
{:stacks=>[[], [], [1, 0]], :moves=>8, :moved=>{:disc=>0, :from=>1, :to=>2}, :ternary=>"22"}
|
57
57
|
```
|
58
58
|
where `moved` is the disc that was moved last
|
59
59
|
|
@@ -61,8 +61,8 @@ where `moved` is the disc that was moved last
|
|
61
61
|
|
62
62
|
In order to over-engineer this, I've wrapped a [very thin Flask app](https://github.com/pikesley/pHAT-REST) around the [MicroDot pHAT](https://shop.pimoroni.com/products/microdot-phat). Try
|
63
63
|
|
64
|
-
hanoi phat --phat <address_of_your_pi> --
|
64
|
+
hanoi phat --phat <address_of_your_pi> --interval 0.1
|
65
65
|
|
66
|
-
to watch this all [play out on the pHAT](https://www.youtube.com/watch?v=
|
66
|
+
to watch this all [play out on the pHAT](https://www.youtube.com/watch?v=LT3HNsVxhM8):
|
67
67
|
|
68
|
-
[](https://www.youtube.com/watch?v=LT3HNsVxhM8)
|
data/TODO.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Can I conditionally include a module at constructor time? To e.g. pull in the ConstrainedTowers-specific search methods
|
data/config/config.yml
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
chars:
|
2
|
+
regular:
|
3
|
+
space: ' '
|
4
|
+
disc: 'o'
|
5
|
+
pole: '|'
|
6
|
+
vert_divider: '|'
|
7
|
+
horiz_divider: '-'
|
8
|
+
|
9
|
+
fancy:
|
10
|
+
space: ' '
|
11
|
+
disc: '🎾'
|
12
|
+
pole: '💈'
|
13
|
+
vert_divider: '🔺'
|
14
|
+
horiz_divider: '🔻'
|
15
|
+
|
16
|
+
digits:
|
17
|
+
'0':
|
18
|
+
- [1, 1, 1]
|
19
|
+
- [1, 0, 1]
|
20
|
+
- [1, 1, 1]
|
21
|
+
|
22
|
+
'1':
|
23
|
+
- [0, 1, 0]
|
24
|
+
- [0, 1, 0]
|
25
|
+
- [0, 1, 0]
|
26
|
+
|
27
|
+
'2':
|
28
|
+
- [1, 1, 0]
|
29
|
+
- [0, 1, 0]
|
30
|
+
- [0, 1, 1]
|
data/lib/hanoi/jane.rb
CHANGED
@@ -1,21 +1,39 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'yaml'
|
3
|
+
require 'ostruct'
|
4
|
+
|
1
5
|
require 'thor'
|
2
6
|
require 'httparty'
|
3
7
|
|
4
8
|
require 'hanoi/jane/version'
|
5
9
|
|
6
|
-
require 'hanoi/jane/
|
7
|
-
|
8
|
-
require 'hanoi/jane/
|
10
|
+
require 'hanoi/jane/config'
|
11
|
+
|
12
|
+
require 'hanoi/jane/towers/stack_finders'
|
13
|
+
require 'hanoi/jane/towers/towers'
|
14
|
+
require 'hanoi/jane/towers/constrained_towers'
|
15
|
+
require 'hanoi/jane/towers/animated_towers'
|
16
|
+
|
17
|
+
require 'hanoi/jane/animation/animation'
|
18
|
+
require 'hanoi/jane/animation/lifter'
|
19
|
+
require 'hanoi/jane/animation/dropper'
|
20
|
+
require 'hanoi/jane/animation/padded_stacks'
|
9
21
|
|
10
22
|
require 'hanoi/jane/formatters/matrix'
|
11
23
|
require 'hanoi/jane/formatters/console'
|
12
24
|
|
13
25
|
module Hanoi
|
14
26
|
module Jane
|
15
|
-
def self.hit_phat
|
27
|
+
def self.hit_phat stacks, value, phat
|
28
|
+
matrix = Formatters::Matrix.new do |m|
|
29
|
+
m.stacks = stacks
|
30
|
+
m.digits = value
|
31
|
+
end
|
32
|
+
matrix.populate
|
33
|
+
|
16
34
|
url = "http://#{phat}/lights"
|
17
35
|
payload = {
|
18
|
-
matrix:
|
36
|
+
matrix: matrix
|
19
37
|
}
|
20
38
|
headers = {
|
21
39
|
'Content-Type' => 'application/json',
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Hanoi
|
2
|
+
module Jane
|
3
|
+
class Animation
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
attr_accessor :stacks, :disc, :from, :to, :height
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@stacks = [[0]]
|
10
|
+
@disc = 0
|
11
|
+
@from = 0
|
12
|
+
@to = 1
|
13
|
+
@height = 7
|
14
|
+
|
15
|
+
yield self if block_given?
|
16
|
+
|
17
|
+
@stacks = PaddedStacks.new @stacks, @height
|
18
|
+
@lifter = Lifter.new @stacks[@from]
|
19
|
+
@dropper = Dropper.new @stacks[@to], @disc
|
20
|
+
end
|
21
|
+
|
22
|
+
def each
|
23
|
+
@lifter.each do |state|
|
24
|
+
@stacks[@from] = state.to_a
|
25
|
+
yield self
|
26
|
+
end
|
27
|
+
|
28
|
+
@dropper.each do |state|
|
29
|
+
@stacks[@to] = state.to_a
|
30
|
+
yield self
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Hanoi
|
2
|
+
module Jane
|
3
|
+
class Dropper < Array
|
4
|
+
def initialize stack, item
|
5
|
+
stack.map { |i| self.push i }
|
6
|
+
@item = item
|
7
|
+
end
|
8
|
+
|
9
|
+
def drop
|
10
|
+
pos = Dropper.position self, @item
|
11
|
+
self[pos] = @item
|
12
|
+
|
13
|
+
unless pos >= self.length - 1
|
14
|
+
self[pos + 1] = nil
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def dropped
|
19
|
+
(self[(Dropper.position self, @item) - 1] || self[(Dropper.position self, @item)]) || (Dropper.position self, @item) == 0
|
20
|
+
end
|
21
|
+
|
22
|
+
def each
|
23
|
+
until dropped
|
24
|
+
drop
|
25
|
+
yield self
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def Dropper.position stack, item
|
30
|
+
pos = stack.index item
|
31
|
+
return stack.length - 1 unless pos
|
32
|
+
pos - 1
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Hanoi
|
2
|
+
module Jane
|
3
|
+
class Lifter < Array
|
4
|
+
attr_reader :lifted
|
5
|
+
|
6
|
+
def initialize stack
|
7
|
+
stack.map { |i| self.push i }
|
8
|
+
|
9
|
+
@lifted = false
|
10
|
+
end
|
11
|
+
|
12
|
+
def lift
|
13
|
+
start = Lifter.position self
|
14
|
+
|
15
|
+
item = self[start]
|
16
|
+
self[start] = nil
|
17
|
+
|
18
|
+
next_pos = start + 1
|
19
|
+
if next_pos >= self.length
|
20
|
+
@lifted = true
|
21
|
+
else
|
22
|
+
self[next_pos] = item
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def each
|
27
|
+
until @lifted
|
28
|
+
lift
|
29
|
+
yield self
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def Lifter.position stack
|
34
|
+
pos = nil
|
35
|
+
stack.reverse.each_with_index do |item, index|
|
36
|
+
if item
|
37
|
+
pos = index
|
38
|
+
break
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
stack.length - 1 - pos
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Hanoi
|
2
|
+
module Jane
|
3
|
+
class PaddedStacks < Array
|
4
|
+
def initialize stacks, height = 7
|
5
|
+
stacks.clone.each do |s|
|
6
|
+
self.push PaddedStacks.pad s.clone, height
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def PaddedStacks.pad stack, height
|
11
|
+
stack + Array.new(height - stack.length)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/hanoi/jane/cli.rb
CHANGED
@@ -12,73 +12,58 @@ module Hanoi
|
|
12
12
|
desc 'phat', "Solve the towers against the pHAT's webserver"
|
13
13
|
option :phat, type: :string, required: true
|
14
14
|
option :constrained, type: :boolean
|
15
|
-
option :interval, type: :numeric, default: 0.
|
16
|
-
option :animated, type: :boolean, default: false
|
15
|
+
option :interval, type: :numeric, default: 0.1
|
17
16
|
|
18
17
|
def phat
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
at = AnimatedTowers.new do |a|
|
19
|
+
a.towers = ConstrainedTowers
|
20
|
+
a.discs = 5
|
21
|
+
a.height = 7
|
22
22
|
end
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
if
|
28
|
-
|
29
|
-
Hanoi::Jane.hit_phat frame, options[:phat]
|
30
|
-
sleep options[:interval] * 0.1
|
31
|
-
end
|
24
|
+
at.each do |frame|
|
25
|
+
Hanoi::Jane.hit_phat frame.stacks, frame.value, options[:phat]
|
26
|
+
interval = options[:interval]
|
27
|
+
if frame.type == :tween
|
28
|
+
interval = interval * 0.1
|
32
29
|
end
|
33
|
-
|
34
|
-
Hanoi::Jane.hit_phat towers, options[:phat]
|
35
|
-
sleep options[:interval]
|
30
|
+
sleep interval
|
36
31
|
end
|
37
32
|
end
|
38
33
|
|
39
34
|
desc 'console', 'Solve the towers on the console'
|
40
|
-
option :discs, type: :numeric, default: 3
|
41
|
-
option :constrained, type: :boolean
|
35
|
+
option :discs, type: :numeric, default: 3
|
36
|
+
option :constrained, type: :boolean, default: true
|
37
|
+
option :interval, type: :numeric, default: 0.5
|
38
|
+
option :height, type: :numeric, default: 2
|
42
39
|
option :fancy, type: :boolean, default: false
|
43
|
-
option :animated, type: :boolean, default: false
|
44
|
-
option :delay, type: :numeric, default: 0.2
|
45
40
|
|
46
41
|
def console
|
47
|
-
|
48
|
-
|
49
|
-
|
42
|
+
at = AnimatedTowers.new do |a|
|
43
|
+
a.towers = options[:constrained] ? ConstrainedTowers : Towers
|
44
|
+
a.discs = options[:discs]
|
45
|
+
a.height = options[:discs] + options[:height]
|
50
46
|
end
|
51
47
|
|
52
|
-
|
48
|
+
at.each do |frame|
|
49
|
+
system('clear')
|
53
50
|
|
54
|
-
|
55
|
-
|
56
|
-
|
51
|
+
c = Formatters::Console.new do |c|
|
52
|
+
c.stacks = frame.stacks
|
53
|
+
c.fancy = options[:fancy]
|
54
|
+
end
|
57
55
|
|
58
|
-
|
59
|
-
|
56
|
+
puts frame.value
|
57
|
+
puts c
|
60
58
|
|
61
|
-
|
62
|
-
if
|
63
|
-
|
64
|
-
system('clear')
|
65
|
-
s = options[:fancy] ? (Formatters::Console.fancify state.rebased) : state.rebased
|
66
|
-
puts s
|
67
|
-
puts
|
68
|
-
puts frame
|
69
|
-
sleep options[:delay] * 0.2
|
70
|
-
end
|
59
|
+
interval = options[:interval]
|
60
|
+
if frame.type == :tween
|
61
|
+
interval = interval * 0.1
|
71
62
|
end
|
72
|
-
|
73
|
-
system('clear')
|
74
|
-
s = options[:fancy] ? (Formatters::Console.fancify state.rebased) : state.rebased
|
75
|
-
puts s
|
76
|
-
puts
|
77
|
-
puts state.console
|
78
|
-
sleep options[:delay]
|
63
|
+
sleep interval
|
79
64
|
end
|
80
65
|
|
81
|
-
puts '%d moves to solve for %d discs' % [towers.total,
|
66
|
+
puts '%d moves to solve for %d discs' % [at.towers.total, at.discs]
|
82
67
|
end
|
83
68
|
end
|
84
69
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Hanoi
|
2
|
+
module Jane
|
3
|
+
class Config
|
4
|
+
include Singleton
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
reset!
|
8
|
+
end
|
9
|
+
|
10
|
+
def reset! # testing a singleton is hard
|
11
|
+
@config = OpenStruct.new fetch_yaml 'config'
|
12
|
+
end
|
13
|
+
|
14
|
+
def config
|
15
|
+
@config
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def fetch_yaml file
|
21
|
+
YAML.load(File.open(File.join(File.dirname(__FILE__), '..', '..', '..', 'config/%s.yml' % file)))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -2,65 +2,68 @@ module Hanoi
|
|
2
2
|
module Jane
|
3
3
|
module Formatters
|
4
4
|
class Console
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
}
|
12
|
-
|
13
|
-
FANCY_CHARS = {
|
14
|
-
space: ' ',
|
15
|
-
disc: '🎾',
|
16
|
-
pole: '💈',
|
17
|
-
vert_divider: '🔺',
|
18
|
-
horiz_divider: '🔻'
|
19
|
-
}
|
20
|
-
|
21
|
-
def initialize discs, stacks, fancy = false
|
22
|
-
@discs = discs
|
23
|
-
@stacks = stacks.clone.map { |s| s.clone }
|
24
|
-
@@chars = fancy ? FANCY_CHARS : CHARS
|
5
|
+
attr_accessor :stacks, :fancy
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@fancy = false
|
9
|
+
|
10
|
+
yield self if block_given?
|
25
11
|
end
|
26
12
|
|
27
13
|
def to_s
|
28
|
-
|
29
|
-
|
14
|
+
(Console.populate stacks, @fancy).map { |r| r.join '' }.join "\n"
|
15
|
+
end
|
30
16
|
|
31
|
-
|
32
|
-
|
17
|
+
def Console.assemble stacks
|
18
|
+
a = []
|
19
|
+
(Console.rotate stacks).each_with_index do |r, index|
|
20
|
+
divided = (index + 1) == stacks.first.length ? true : false
|
21
|
+
a.push Console.row r, widest: Console.biggest(stacks), divided: divided
|
22
|
+
end
|
33
23
|
|
34
|
-
|
35
|
-
joiner,
|
36
|
-
stack.map { |s| Console.make_disc s, (Console.scale @discs) }.join(joiner),
|
37
|
-
joiner
|
38
|
-
]
|
24
|
+
a.push [:horiz_divider] * a[0].length
|
39
25
|
|
40
|
-
|
26
|
+
a
|
27
|
+
end
|
41
28
|
|
42
|
-
|
29
|
+
def Console.populate stacks, fancy = false
|
30
|
+
charset = fancy ? 'fancy' : 'regular'
|
31
|
+
(Console.assemble stacks).map do
|
32
|
+
|r| r.map do |c|
|
33
|
+
Config.instance.config.chars[charset][c.to_s]
|
34
|
+
end
|
35
|
+
end
|
43
36
|
end
|
44
37
|
|
45
|
-
def Console.
|
46
|
-
|
38
|
+
def Console.biggest stacks
|
39
|
+
stacks.map { |s| s.compact.max }.compact.max
|
47
40
|
end
|
48
41
|
|
49
|
-
def Console.
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
42
|
+
def Console.disc size, width
|
43
|
+
return [:space] * Console.scale(width) unless size
|
44
|
+
content = Console.scale size
|
45
|
+
gap = (Console.scale(width) - content) / 2
|
46
|
+
|
47
|
+
output = [:disc] * content
|
48
|
+
|
49
|
+
gap.times do
|
50
|
+
[:unshift, :push].each do |method|
|
51
|
+
output.send(method, :space)
|
52
|
+
end
|
54
53
|
end
|
55
54
|
|
56
|
-
|
57
|
-
|
55
|
+
output
|
56
|
+
end
|
57
|
+
|
58
|
+
def Console.row starter, widest:, spacing: 1, divided: false
|
59
|
+
filler = [:space] * spacing
|
60
|
+
filler = [:vert_divider] if divided
|
58
61
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
62
|
+
starter.map { |d|
|
63
|
+
Console.disc d, widest
|
64
|
+
}.flat_map { |d|
|
65
|
+
[d, filler]
|
66
|
+
}.unshift(filler).flatten
|
64
67
|
end
|
65
68
|
|
66
69
|
def Console.scale size
|
@@ -70,11 +73,6 @@ module Hanoi
|
|
70
73
|
def Console.rotate stacks
|
71
74
|
stacks.map { |s| s.clone }.transpose.reverse
|
72
75
|
end
|
73
|
-
|
74
|
-
def Console.fancify number
|
75
|
-
fancy_digits = '012'
|
76
|
-
number.chars.map { |d| fancy_digits[d] }.join ' '
|
77
|
-
end
|
78
76
|
end
|
79
77
|
end
|
80
78
|
end
|
@@ -2,73 +2,86 @@ module Hanoi
|
|
2
2
|
module Jane
|
3
3
|
module Formatters
|
4
4
|
class Matrix < Array
|
5
|
-
|
6
|
-
|
7
|
-
@digits = towers.rebased
|
5
|
+
attr_accessor :stacks
|
6
|
+
attr_reader :digits, :bit_offset
|
8
7
|
|
9
|
-
|
8
|
+
def initialize
|
9
|
+
@digits = '0'
|
10
|
+
@stacks = [[]]
|
11
|
+
|
12
|
+
@bit_offset = 24
|
13
|
+
@bit_side = :right
|
14
|
+
@valid_digits = Config.instance.config.digits
|
15
|
+
|
16
|
+
yield self if block_given?
|
17
|
+
|
18
|
+
#populate
|
10
19
|
end
|
11
20
|
|
12
|
-
def
|
13
|
-
@
|
14
|
-
|
21
|
+
def digits= digits
|
22
|
+
if digits.chars.reject { |d| @valid_digits.keys.include? d }.length > 0
|
23
|
+
raise MatrixException.new '%s is not a valid value for digits' % digits
|
24
|
+
end
|
25
|
+
|
26
|
+
if digits.length > 5
|
27
|
+
raise MatrixException.new '%s is longer than 5 chars' % digits
|
28
|
+
end
|
29
|
+
|
30
|
+
@digits = digits
|
15
31
|
end
|
16
32
|
|
17
|
-
def
|
33
|
+
def wipe
|
18
34
|
7.times do |i|
|
19
35
|
self[i] = [0] * 45
|
20
36
|
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def draw_disc disc, height = 0, offset = 0
|
40
|
+
if disc
|
41
|
+
(disc + 1).times do |i|
|
42
|
+
self[6 - height][i + offset + (Matrix.shim disc)] = 1
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def draw_stack stack, offset = 0
|
48
|
+
height = 0
|
49
|
+
stack.each do |disc|
|
50
|
+
draw_disc disc, height, offset
|
51
|
+
height += 1
|
52
|
+
end
|
53
|
+
end
|
21
54
|
|
55
|
+
def populate
|
56
|
+
wipe
|
57
|
+
draw_stacks
|
58
|
+
draw_digits
|
59
|
+
end
|
60
|
+
|
61
|
+
def draw_stacks
|
22
62
|
offset = 0
|
23
63
|
@stacks.each do |stack|
|
24
|
-
|
25
|
-
stack.each do |disc|
|
26
|
-
if disc
|
27
|
-
shim = ((5 - (disc + 1)) / 2).round
|
28
|
-
(disc + 1).times do |i|
|
29
|
-
self[6 - total][i + offset + shim] = 1
|
30
|
-
end
|
31
|
-
end
|
32
|
-
total += 1
|
33
|
-
end
|
64
|
+
draw_stack stack, offset
|
34
65
|
offset += 8
|
35
66
|
end
|
67
|
+
end
|
36
68
|
|
37
|
-
|
38
|
-
@bit_side = :right
|
69
|
+
def draw_digits
|
39
70
|
@digits.chars.each do |bit|
|
40
71
|
digit bit
|
72
|
+
@bit_side = realign @bit_side
|
73
|
+
end
|
74
|
+
end
|
41
75
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
@bit_side = :right
|
47
|
-
end
|
76
|
+
def realign side
|
77
|
+
if side == :right
|
78
|
+
@bit_offset += 8
|
79
|
+
return :left
|
48
80
|
end
|
81
|
+
:right
|
49
82
|
end
|
50
83
|
|
51
84
|
def digit value
|
52
|
-
digits = {
|
53
|
-
0 => [
|
54
|
-
[1, 1, 1],
|
55
|
-
[1, 0, 1],
|
56
|
-
[1, 1, 1]
|
57
|
-
],
|
58
|
-
|
59
|
-
1 => [
|
60
|
-
[0, 1, 0],
|
61
|
-
[0, 1, 0],
|
62
|
-
[0, 1, 0]
|
63
|
-
],
|
64
|
-
|
65
|
-
2 => [
|
66
|
-
[1, 1, 0],
|
67
|
-
[0, 1, 0],
|
68
|
-
[0, 1, 1]
|
69
|
-
]
|
70
|
-
}
|
71
|
-
|
72
85
|
@column = @bit_offset
|
73
86
|
@row = 0
|
74
87
|
if @bit_side == :right
|
@@ -76,17 +89,28 @@ module Hanoi
|
|
76
89
|
@row = 4
|
77
90
|
end
|
78
91
|
|
79
|
-
insert
|
80
|
-
|
92
|
+
insert value, @row, @column
|
81
93
|
end
|
82
94
|
|
83
|
-
def insert
|
95
|
+
def insert value, row = 0, column = 0
|
84
96
|
3.times do |i|
|
85
97
|
3.times do |j|
|
86
|
-
self[
|
98
|
+
self[row + i][column + j] = @valid_digits[value][i][j]
|
87
99
|
end
|
88
100
|
end
|
89
101
|
end
|
102
|
+
|
103
|
+
def Matrix.shim size
|
104
|
+
((5 - (size + 1)) / 2).round
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
class MatrixException < Exception
|
109
|
+
attr_reader :text
|
110
|
+
|
111
|
+
def initialize text
|
112
|
+
@text = text
|
113
|
+
end
|
90
114
|
end
|
91
115
|
end
|
92
116
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Hanoi
|
2
|
+
module Jane
|
3
|
+
class AnimatedTowers
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
attr_accessor :towers, :discs, :height
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
yield self if block_given?
|
10
|
+
|
11
|
+
if @discs > @height
|
12
|
+
raise HanoiException.new 'number_of_discs (%d) > height (%d)' % [@height, @discs]
|
13
|
+
end
|
14
|
+
|
15
|
+
@towers = @towers.new @discs
|
16
|
+
end
|
17
|
+
|
18
|
+
def each
|
19
|
+
until @towers.solved
|
20
|
+
stacks = PaddedStacks.new @towers.stacks, @height
|
21
|
+
value = @towers.rebased
|
22
|
+
@towers.move
|
23
|
+
yield Frame.new stacks, value, :key
|
24
|
+
|
25
|
+
@anim = Animation.new do |a|
|
26
|
+
a.stacks = stacks
|
27
|
+
a.disc = @towers.disc
|
28
|
+
a.from = @towers.from
|
29
|
+
a.to = @towers.to
|
30
|
+
a.height = @height
|
31
|
+
end
|
32
|
+
|
33
|
+
@anim.each do |frame|
|
34
|
+
yield Frame.new frame.stacks, @towers.rebased, :tween
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
yield Frame.new (PaddedStacks.new @towers.stacks, @height), @towers.rebased, :key
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class Frame
|
43
|
+
attr_reader :stacks, :type, :value
|
44
|
+
|
45
|
+
def initialize stacks, value, type
|
46
|
+
@stacks = stacks
|
47
|
+
@value = value
|
48
|
+
@type = type
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class HanoiException < Exception
|
53
|
+
attr_reader :text
|
54
|
+
|
55
|
+
def initialize text
|
56
|
+
@text = text
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Hanoi
|
2
|
+
module Jane
|
3
|
+
class ConstrainedTowers < Towers
|
4
|
+
extend ConstrainedStackFinder
|
5
|
+
|
6
|
+
def initialize discs = 3
|
7
|
+
super
|
8
|
+
@base = 3
|
9
|
+
end
|
10
|
+
|
11
|
+
def ternary
|
12
|
+
rebased
|
13
|
+
end
|
14
|
+
|
15
|
+
def inspect
|
16
|
+
i = super
|
17
|
+
i[:ternary] = i.delete :binary
|
18
|
+
i
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Hanoi
|
2
|
+
module Jane
|
3
|
+
module StackFinder
|
4
|
+
def find_stack stacks:, from:, disc:, total: nil
|
5
|
+
# if the next stack is empty, move there
|
6
|
+
if stacks[(from + 1) % 3] == []
|
7
|
+
return (from + 1) % 3
|
8
|
+
end
|
9
|
+
|
10
|
+
# if the next stack has a smaller top disc than our disc, go one more over
|
11
|
+
if stacks[(from + 1) % 3][-1] < disc
|
12
|
+
return (from + 2) % 3
|
13
|
+
end
|
14
|
+
|
15
|
+
# default to the next one
|
16
|
+
return (from + 1) % 3
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module ConstrainedStackFinder
|
21
|
+
def find_stack stacks:, from:, disc: nil, total:
|
22
|
+
# if we're in the middle
|
23
|
+
if from == 1
|
24
|
+
# we always move to the right on an even total
|
25
|
+
if total % 2 == 0
|
26
|
+
return 2
|
27
|
+
else
|
28
|
+
return 0
|
29
|
+
end
|
30
|
+
end
|
31
|
+
# otherwise we're at the edges and can only move to the middle
|
32
|
+
1
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module Hanoi
|
2
|
+
module Jane
|
3
|
+
class Towers
|
4
|
+
extend StackFinder
|
5
|
+
|
6
|
+
attr_reader :stacks, :total, :base, :disc, :from, :to
|
7
|
+
attr_accessor :discs
|
8
|
+
|
9
|
+
def initialize discs = 3
|
10
|
+
@discs = discs
|
11
|
+
@total = 0
|
12
|
+
@base = 2
|
13
|
+
@stacks = Towers.starter_stacks @discs
|
14
|
+
|
15
|
+
yield self if block_given?
|
16
|
+
end
|
17
|
+
|
18
|
+
def discs= discs
|
19
|
+
@discs = discs
|
20
|
+
@stacks = Towers.starter_stacks @discs
|
21
|
+
end
|
22
|
+
|
23
|
+
def move
|
24
|
+
before = binary
|
25
|
+
@total += 1
|
26
|
+
after = binary
|
27
|
+
|
28
|
+
@disc = Towers.diff before, after
|
29
|
+
|
30
|
+
@from = Towers.find_disc @stacks, @disc
|
31
|
+
@to = self.class.find_stack stacks: @stacks, from: @from, disc: @disc, total: @total
|
32
|
+
@stacks[@to].push @stacks[@from].pop
|
33
|
+
end
|
34
|
+
|
35
|
+
def solved
|
36
|
+
rebased.chars.all? { |digit| digit.to_i == @base - 1 }
|
37
|
+
end
|
38
|
+
|
39
|
+
def inspect
|
40
|
+
{
|
41
|
+
stacks: @stacks,
|
42
|
+
moves: @total,
|
43
|
+
binary: rebased,
|
44
|
+
moved: {
|
45
|
+
disc: @disc,
|
46
|
+
from: @from,
|
47
|
+
to: @to
|
48
|
+
}
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
def each
|
53
|
+
yield self if @total == 0
|
54
|
+
until solved
|
55
|
+
move
|
56
|
+
yield self
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_s
|
61
|
+
s = ''
|
62
|
+
@stacks.each do |stack|
|
63
|
+
s += stack.to_s
|
64
|
+
s += "\n"
|
65
|
+
end
|
66
|
+
s += '---'
|
67
|
+
|
68
|
+
s
|
69
|
+
end
|
70
|
+
|
71
|
+
def binary
|
72
|
+
rebased
|
73
|
+
end
|
74
|
+
|
75
|
+
def rebased
|
76
|
+
Towers.rebase @total, @base, @discs
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def Towers.starter_stacks discs
|
82
|
+
[(0...discs).to_a.reverse, [], []]
|
83
|
+
end
|
84
|
+
|
85
|
+
def Towers.diff this, that
|
86
|
+
this.chars.reverse.each_with_index do |bit, index|
|
87
|
+
if bit < that.chars.reverse[index]
|
88
|
+
return index
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def Towers.rebase value, base, width
|
94
|
+
'%0*d' % [width, value.to_s(base)]
|
95
|
+
end
|
96
|
+
|
97
|
+
def Towers.find_disc stacks, disc
|
98
|
+
stacks.each_with_index do |stack, index|
|
99
|
+
return index if stack.index disc
|
100
|
+
end
|
101
|
+
|
102
|
+
raise SearchException.new '%s not found in stacks' % disc
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
class SearchException < Exception
|
107
|
+
attr_reader :text
|
108
|
+
|
109
|
+
def initialize text
|
110
|
+
@text = text
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
data/lib/hanoi/jane/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hanoi-jane
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- pikesley
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-01-
|
11
|
+
date: 2018-01-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -141,17 +141,25 @@ files:
|
|
141
141
|
- LICENSE.md
|
142
142
|
- README.md
|
143
143
|
- Rakefile
|
144
|
+
- TODO.md
|
144
145
|
- bin/console
|
145
146
|
- bin/hanoi
|
146
147
|
- bin/setup
|
148
|
+
- config/config.yml
|
147
149
|
- hanoi-jane.gemspec
|
148
150
|
- lib/hanoi/jane.rb
|
149
|
-
- lib/hanoi/jane/animation.rb
|
151
|
+
- lib/hanoi/jane/animation/animation.rb
|
152
|
+
- lib/hanoi/jane/animation/dropper.rb
|
153
|
+
- lib/hanoi/jane/animation/lifter.rb
|
154
|
+
- lib/hanoi/jane/animation/padded_stacks.rb
|
150
155
|
- lib/hanoi/jane/cli.rb
|
151
|
-
- lib/hanoi/jane/
|
156
|
+
- lib/hanoi/jane/config.rb
|
152
157
|
- lib/hanoi/jane/formatters/console.rb
|
153
158
|
- lib/hanoi/jane/formatters/matrix.rb
|
154
|
-
- lib/hanoi/jane/towers.rb
|
159
|
+
- lib/hanoi/jane/towers/animated_towers.rb
|
160
|
+
- lib/hanoi/jane/towers/constrained_towers.rb
|
161
|
+
- lib/hanoi/jane/towers/stack_finders.rb
|
162
|
+
- lib/hanoi/jane/towers/towers.rb
|
155
163
|
- lib/hanoi/jane/version.rb
|
156
164
|
homepage: http://sam.pikesley.org/projects/hanoi-jane/
|
157
165
|
licenses:
|
data/lib/hanoi/jane/animation.rb
DELETED
@@ -1,79 +0,0 @@
|
|
1
|
-
module Hanoi
|
2
|
-
module Jane
|
3
|
-
class Animation
|
4
|
-
include Enumerable
|
5
|
-
|
6
|
-
attr_reader :stacks
|
7
|
-
|
8
|
-
def initialize towers
|
9
|
-
@towers = towers
|
10
|
-
@height = towers.discs
|
11
|
-
@stacks = Animation.pad towers.old_stacks, @height
|
12
|
-
@disc = towers.disc
|
13
|
-
@source = towers.source
|
14
|
-
@destination = towers.sink
|
15
|
-
@fancy = towers.fancy
|
16
|
-
|
17
|
-
@lift = true
|
18
|
-
@drop = false
|
19
|
-
@done = false
|
20
|
-
end
|
21
|
-
|
22
|
-
def animate
|
23
|
-
@drop = true unless @lift
|
24
|
-
if @lift
|
25
|
-
stack = @stacks[@source]
|
26
|
-
@position = stack.index @disc
|
27
|
-
stack[@position] = nil
|
28
|
-
|
29
|
-
if @position + 1 >= @height
|
30
|
-
@lift = false
|
31
|
-
else
|
32
|
-
stack[@position + 1] = @disc
|
33
|
-
end
|
34
|
-
|
35
|
-
@stacks[@source] = stack
|
36
|
-
end
|
37
|
-
|
38
|
-
if @drop
|
39
|
-
stack = @stacks[@destination]
|
40
|
-
@position = -1 unless @position
|
41
|
-
|
42
|
-
stack[@position] = @disc
|
43
|
-
stack[(stack.index @disc) + 1] = nil
|
44
|
-
|
45
|
-
@position -= 1
|
46
|
-
|
47
|
-
@stacks[@destination] = stack.take @height
|
48
|
-
|
49
|
-
if stack[@position] || @position == 0
|
50
|
-
@drop = false
|
51
|
-
@done = true
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def to_s
|
57
|
-
(Formatters::Console.new @height, @stacks, @fancy).to_s
|
58
|
-
end
|
59
|
-
|
60
|
-
def matrix
|
61
|
-
m = Formatters::Matrix.new @towers
|
62
|
-
m.stacks = @stacks
|
63
|
-
|
64
|
-
m
|
65
|
-
end
|
66
|
-
|
67
|
-
def each
|
68
|
-
until @done
|
69
|
-
animate
|
70
|
-
yield self
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
def Animation.pad stacks, height
|
75
|
-
stacks.map { |stack| stack + Array.new(height - stack.length) }
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
@@ -1,36 +0,0 @@
|
|
1
|
-
module Hanoi
|
2
|
-
module Jane
|
3
|
-
class ConstrainedTowers < Towers
|
4
|
-
def initialize discs
|
5
|
-
super
|
6
|
-
@base = 3
|
7
|
-
end
|
8
|
-
|
9
|
-
def ternary
|
10
|
-
rebased
|
11
|
-
end
|
12
|
-
|
13
|
-
def inspect
|
14
|
-
i = super
|
15
|
-
i[:ternary] = i.delete :binary
|
16
|
-
i
|
17
|
-
end
|
18
|
-
|
19
|
-
private
|
20
|
-
|
21
|
-
def find_stack
|
22
|
-
# if we're in the middle
|
23
|
-
if @source == 1
|
24
|
-
# we always move to the right on an even total
|
25
|
-
if @total % 2 == 0
|
26
|
-
return 2
|
27
|
-
else
|
28
|
-
return 0
|
29
|
-
end
|
30
|
-
end
|
31
|
-
# otherwise we're at the edges and can only move to the middle
|
32
|
-
1
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
data/lib/hanoi/jane/towers.rb
DELETED
@@ -1,122 +0,0 @@
|
|
1
|
-
module Hanoi
|
2
|
-
module Jane
|
3
|
-
class Towers
|
4
|
-
include Enumerable
|
5
|
-
|
6
|
-
attr_reader :total, :stacks, :discs, :old_stacks, :disc, :source, :sink
|
7
|
-
attr_accessor :fancy, :animated, :animation
|
8
|
-
|
9
|
-
def initialize discs
|
10
|
-
@discs = discs
|
11
|
-
@total = 0
|
12
|
-
@base = 2
|
13
|
-
@stacks = [(0...discs).to_a.reverse, [], []]
|
14
|
-
@fancy = false
|
15
|
-
@animated = false
|
16
|
-
end
|
17
|
-
|
18
|
-
def move
|
19
|
-
@old_stacks = @stacks.clone.map { |s| s.clone }
|
20
|
-
|
21
|
-
diff
|
22
|
-
@source = find_disc
|
23
|
-
@sink = find_stack
|
24
|
-
@stacks[@sink].push @stacks[@source].pop
|
25
|
-
|
26
|
-
if @animated
|
27
|
-
@animation = Animation.new self
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def solved
|
32
|
-
rebased.chars.all? { |digit| digit.to_i == @base - 1 }
|
33
|
-
end
|
34
|
-
|
35
|
-
def matrix
|
36
|
-
Formatters::Matrix.new self
|
37
|
-
end
|
38
|
-
|
39
|
-
def console
|
40
|
-
(Formatters::Console.new @discs, @stacks, @fancy).to_s
|
41
|
-
end
|
42
|
-
|
43
|
-
def inspect
|
44
|
-
{
|
45
|
-
stacks: @stacks,
|
46
|
-
moves: @total,
|
47
|
-
binary: rebased,
|
48
|
-
moved: {
|
49
|
-
disc: @disc,
|
50
|
-
from: @source,
|
51
|
-
to: @sink
|
52
|
-
}
|
53
|
-
}
|
54
|
-
end
|
55
|
-
|
56
|
-
def each
|
57
|
-
yield self if @total == 0
|
58
|
-
until solved
|
59
|
-
move
|
60
|
-
yield self
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
def to_s
|
65
|
-
s = ''
|
66
|
-
@stacks.each do |stack|
|
67
|
-
s += stack.to_s
|
68
|
-
s += "\n"
|
69
|
-
end
|
70
|
-
s += '---'
|
71
|
-
|
72
|
-
s
|
73
|
-
end
|
74
|
-
|
75
|
-
def binary
|
76
|
-
rebased
|
77
|
-
end
|
78
|
-
|
79
|
-
def rebased
|
80
|
-
Towers.rebase @total, @base, @discs
|
81
|
-
end
|
82
|
-
|
83
|
-
private
|
84
|
-
|
85
|
-
def find_disc
|
86
|
-
@stacks.each_with_index do |stack, index|
|
87
|
-
return index if stack.index @disc
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
def find_stack
|
92
|
-
# if the next stack is empty, move there
|
93
|
-
if @stacks[(@source + 1) % 3] == []
|
94
|
-
return (@source + 1) % 3
|
95
|
-
end
|
96
|
-
|
97
|
-
# if the next stack has a smaller top disc than our disc, go one more over
|
98
|
-
if @stacks[(@source + 1) % 3][-1] < @disc
|
99
|
-
return (@source + 2) % 3
|
100
|
-
end
|
101
|
-
|
102
|
-
# default to the next one
|
103
|
-
return (@source + 1) % 3
|
104
|
-
end
|
105
|
-
|
106
|
-
def diff
|
107
|
-
this = binary
|
108
|
-
@total += 1
|
109
|
-
that = binary
|
110
|
-
this.chars.reverse.each_with_index do |bit, index|
|
111
|
-
if bit < that.chars.reverse[index]
|
112
|
-
@disc = index
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
def Towers.rebase value, base, width
|
118
|
-
'%0*d' % [width, value.to_s(base)]
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|