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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8d7f114e32d1a1e8f3a9f59cf07c86c0d68c9a17
4
- data.tar.gz: b78e6dbd9b7deffc063e60049e22888183742a7f
3
+ metadata.gz: c8a1d92da6a843d6068462542eb6728d80995eed
4
+ data.tar.gz: dbac7d2f1d65726fc56aad01a1653f8818915e22
5
5
  SHA512:
6
- metadata.gz: 695210a096216a3c91bdf43e8ff86cc3130cf8657bf5a6ef66c6f1da2ce17ca0bc26cb9f1420ba02c3e5d1c21ee03610afa9f9140a0919529ae64959b42111d4
7
- data.tar.gz: bc72d1599263f725be624fb56c4d7ee6e488d44658f6627a2620a40b1e1eb062b14ce0048ababc968439abbd329c0c9e5a0c7b298a49db5a3c7cee693e703ee7
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=>'00'}
49
- {:stacks=>[[1], [0], []], :moves=>1, :moved=>0, :ternary=>'01'}
50
- {:stacks=>[[1], [], [0]], :moves=>2, :moved=>0, :ternary=>'02'}
51
- {:stacks=>[[], [1], [0]], :moves=>3, :moved=>1, :ternary=>'10'}
52
- {:stacks=>[[], [1, 0], []], :moves=>4, :moved=>0, :ternary=>'11'}
53
- {:stacks=>[[0], [1], []], :moves=>5, :moved=>0, :ternary=>'12'}
54
- {:stacks=>[[0], [], [1]], :moves=>6, :moved=>1, :ternary=>'20'}
55
- {:stacks=>[[], [0], [1]], :moves=>7, :moved=>0, :ternary=>'21'}
56
- {:stacks=>[[], [], [1, 0]], :moves=>8, :moved=>0, :ternary=>'22'}
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> --constrained
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=PAQY5XtdNO8):
66
+ to watch this all [play out on the pHAT](https://www.youtube.com/watch?v=LT3HNsVxhM8):
67
67
 
68
- [![Video](https://i.imgur.com/QILZYgx.png)](https://www.youtube.com/watch?v=PAQY5XtdNO8)
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
@@ -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]
@@ -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/towers'
7
- require 'hanoi/jane/constrained_towers'
8
- require 'hanoi/jane/animation'
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 towers, 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: towers.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
@@ -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.3
16
- option :animated, type: :boolean, default: false
15
+ option :interval, type: :numeric, default: 0.1
17
16
 
18
17
  def phat
19
- towers = Towers.new 5
20
- if options[:constrained]
21
- towers = ConstrainedTowers.new 5
18
+ at = AnimatedTowers.new do |a|
19
+ a.towers = ConstrainedTowers
20
+ a.discs = 5
21
+ a.height = 7
22
22
  end
23
23
 
24
- towers.animated = options[:animated]
25
-
26
- towers.each do |state|
27
- if state.animation
28
- state.animation.each do |frame|
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, minimum: 1
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
- if options[:discs] < 1
48
- puts "Solving for %d discs makes no sense" % options[:discs]
49
- exit 1
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
- towers = Towers.new options[:discs]
48
+ at.each do |frame|
49
+ system('clear')
53
50
 
54
- if options[:constrained]
55
- towers = ConstrainedTowers.new options[:discs]
56
- end
51
+ c = Formatters::Console.new do |c|
52
+ c.stacks = frame.stacks
53
+ c.fancy = options[:fancy]
54
+ end
57
55
 
58
- towers.fancy = options[:fancy]
59
- towers.animated = options[:animated]
56
+ puts frame.value
57
+ puts c
60
58
 
61
- towers.each do |state|
62
- if state.animation
63
- state.animation.each do |frame|
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, options[:discs]]
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
- CHARS = {
6
- space: ' ',
7
- disc: 'o',
8
- pole: '|',
9
- vert_divider: '|',
10
- horiz_divider: '-'
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
- s = ''
29
- joiner = @@chars[:space]
14
+ (Console.populate stacks, @fancy).map { |r| r.join '' }.join "\n"
15
+ end
30
16
 
31
- (Console.rotate @stacks.map { |s| (Console.pad s, @discs).reverse }).each_with_index do |stack, i|
32
- joiner = @@chars[:vert_divider] if i == @discs
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
- s += "%s%s%s\n" % [
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
- end
26
+ a
27
+ end
41
28
 
42
- s += "%s\n" % [@@chars[:horiz_divider] * (4 + (Console.scale @discs) * 3)]
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.pad array, length
46
- Array.new(length - array.length) + array.reverse
38
+ def Console.biggest stacks
39
+ stacks.map { |s| s.compact.max }.compact.max
47
40
  end
48
41
 
49
- def Console.make_disc width, space
50
- char = @@chars[:disc]
51
- unless width
52
- width = 0
53
- char = @@chars[:pole]
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
- count = Console.scale width
57
- padding = (space - count) / 2
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
- '%s%s%s' % [
60
- @@chars[:space] * padding,
61
- char * count,
62
- @@chars[:space] * padding
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
- def initialize towers
6
- @stacks = towers.stacks
7
- @digits = towers.rebased
5
+ attr_accessor :stacks
6
+ attr_reader :digits, :bit_offset
8
7
 
9
- populate
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 stacks= stacks
13
- @stacks = stacks
14
- populate
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 populate
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
- total = 0
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
- @bit_offset = 24
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
- if @bit_side == :right
43
- @bit_side = :left
44
- @bit_offset += 8
45
- else
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 digits[value.to_i]
80
-
92
+ insert value, @row, @column
81
93
  end
82
94
 
83
- def insert grid
95
+ def insert value, row = 0, column = 0
84
96
  3.times do |i|
85
97
  3.times do |j|
86
- self[@row + i][@column + j] = grid[i][j]
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
@@ -1,5 +1,5 @@
1
1
  module Hanoi
2
2
  module Jane
3
- VERSION = '0.2.5'
3
+ VERSION = '0.3.0'
4
4
  end
5
5
  end
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.2.5
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-14 00:00:00.000000000 Z
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/constrained_towers.rb
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:
@@ -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
@@ -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