hanoi-jane 0.2.5 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
[![
|
68
|
+
[![Screenshot](https://i.imgur.com/yrK3isK.png)](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
|