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 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