rotor_machine 1.0.9 → 1.0.10
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/Guardfile +40 -0
- data/lib/rotor_machine/factory.rb +195 -0
- data/lib/rotor_machine/machine.rb +5 -11
- data/lib/rotor_machine/plugboard.rb +5 -7
- data/lib/rotor_machine/reflector.rb +44 -6
- data/lib/rotor_machine/version.rb +1 -1
- data/rotor_machine.gemspec +3 -0
- metadata +32 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 33525b02fa9c5e61d56bb3067a51ae60ce50fd412d1836786fa30e940c9e9950
|
4
|
+
data.tar.gz: 98e386fcbf3d6bca1a1a69c9e362f14239358c899082f826fc11abec0a1275a1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 82cc734ccd263bd2c337852db5cd7ab89bef8f684da390689940875714187c81fc5de77d357fa4484368eae4a7c4819ba40d12e98aff239c310cf9e61297b4e0
|
7
|
+
data.tar.gz: d0f5dcecf48866ff9f0d903ae7c849f7b5cb0dd9dbb6cf18c832cee1db99aea8a7caa225b95cc561ddd897440493b59573a07db24fdfed94e3d3344b0dae5a05
|
data/Guardfile
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
## Uncomment and set this to only include directories you want to watch
|
5
|
+
# directories %w(app lib config test spec features) \
|
6
|
+
# .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
|
7
|
+
|
8
|
+
## Note: if you are using the `directories` clause above and you are not
|
9
|
+
## watching the project directory ('.'), then you will want to move
|
10
|
+
## the Guardfile to a watched dir and symlink it back, e.g.
|
11
|
+
#
|
12
|
+
# $ mkdir config
|
13
|
+
# $ mv Guardfile config/
|
14
|
+
# $ ln -s config/Guardfile .
|
15
|
+
#
|
16
|
+
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
|
17
|
+
|
18
|
+
# Note: The cmd option is now required due to the increasing number of ways
|
19
|
+
# rspec may be run, below are examples of the most common uses.
|
20
|
+
# * bundler: 'bundle exec rspec'
|
21
|
+
# * bundler binstubs: 'bin/rspec'
|
22
|
+
# * spring: 'bin/rspec' (This will use spring if running and you have
|
23
|
+
# installed the spring binstubs per the docs)
|
24
|
+
# * zeus: 'zeus rspec' (requires the server to be started separately)
|
25
|
+
# * 'just' rspec: 'rspec'
|
26
|
+
|
27
|
+
guard :rspec, cmd: "bundle exec rspec" do
|
28
|
+
require "guard/rspec/dsl"
|
29
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
30
|
+
|
31
|
+
# RSpec files
|
32
|
+
rspec = dsl.rspec
|
33
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
34
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
35
|
+
watch(rspec.spec_files)
|
36
|
+
|
37
|
+
# Ruby files
|
38
|
+
ruby = dsl.ruby
|
39
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
40
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
module RotorMachine
|
2
|
+
##
|
3
|
+
# The {RotorMachine::Factory} provides Factory-pattern helpers to build
|
4
|
+
# various parts of the {RotorMachine} - rotors, reflectors, plugboards,
|
5
|
+
# and fully-configured machines.
|
6
|
+
module Factory
|
7
|
+
extend self
|
8
|
+
|
9
|
+
##
|
10
|
+
# Build a new {Rotor} and return it.
|
11
|
+
#
|
12
|
+
# The options hash for this method can accept the following named
|
13
|
+
# arguments:
|
14
|
+
#
|
15
|
+
# `:rotor_kind` - The type of rotor to create. Should be a symbol matching
|
16
|
+
# a rotor type constant in the {RotorMachine::Rotor} class,
|
17
|
+
# or a 26-character string giving the letter sequence for
|
18
|
+
# the rotor. Defaults to `:ROTOR_1` if not specified.
|
19
|
+
#
|
20
|
+
# `:initial_position` - The initial position of the rotor (0-based
|
21
|
+
# numeric position, or a letter on the rotor.) Defaults
|
22
|
+
# to 0 if not specified.
|
23
|
+
#
|
24
|
+
# `:step_size` - How many positions the rotor should advance with each
|
25
|
+
# step. Defaults to 1 if not specified.
|
26
|
+
#
|
27
|
+
# @param options [Hash] The options hash containing the options for the
|
28
|
+
# rotor.
|
29
|
+
# @return The newly-built rotor.
|
30
|
+
def build_rotor(options={})
|
31
|
+
rotor_kind = options.fetch(:rotor_kind, :ROTOR_I)
|
32
|
+
initial_position = options.fetch(:initial_position, 0)
|
33
|
+
step_size = options.fetch(:step_size, 1)
|
34
|
+
|
35
|
+
rotor_alphabet = nil
|
36
|
+
|
37
|
+
if (rotor_kind.class.name == "Symbol")
|
38
|
+
raise ArgumentError, "Invalid rotor kind (symbol #{rotor_kind} not found)" unless RotorMachine::Rotor.constants.include?(rotor_kind)
|
39
|
+
rotor_alphabet = RotorMachine::Rotor.const_get(rotor_kind)
|
40
|
+
elsif (rotor_kind.class.name == "String")
|
41
|
+
raise ArgumentError, "Invalid rotor kind (invalid length)" unless rotor_kind.length == 26
|
42
|
+
rotor_alphabet = rotor_kind.upcase
|
43
|
+
else
|
44
|
+
raise ArgumentError, "Invalid rotor kind (invalid type #{rotor_kind.class.name})"
|
45
|
+
end
|
46
|
+
|
47
|
+
if initial_position.is_a? Numeric
|
48
|
+
raise ArgumentError, "Invalid position (#{initial_position} out of range)" unless ((0..25).include?(initial_position))
|
49
|
+
elsif initial_position.is_a? String
|
50
|
+
unless RotorMachine::Rotor::ALPHABET.include?(initial_position)
|
51
|
+
raise ArgumentError, "Invalid position (invalid letter '#{initial_position}')"
|
52
|
+
end
|
53
|
+
else
|
54
|
+
raise ArgumentError, "Invalid position (invalid type #{initial_position.class.name})"
|
55
|
+
end
|
56
|
+
|
57
|
+
if step_size.is_a? Numeric
|
58
|
+
raise ArgumentError, "Invalid step size (#{step_size} out of range)" unless ((1..25).include?(step_size))
|
59
|
+
else
|
60
|
+
raise ArgumentError, "Invalid step size (invalid type #{step_size.class.name})"
|
61
|
+
end
|
62
|
+
|
63
|
+
return RotorMachine::Rotor.new(rotor_alphabet, initial_position, step_size)
|
64
|
+
end
|
65
|
+
|
66
|
+
##
|
67
|
+
# Build a new {Reflector} and return it.
|
68
|
+
#
|
69
|
+
# The options hash for this method can accept the following named
|
70
|
+
# arguments:
|
71
|
+
#
|
72
|
+
# `:reflector_kind` - The type of reflector to create. Should be a symbol matching
|
73
|
+
# a reflector type constant in the {RotorMachine::Reflector} class,
|
74
|
+
# or a 26-character string giving the letter sequence for
|
75
|
+
# the reflector. Defaults to `:REFLECTOR_A` if not specified.
|
76
|
+
#
|
77
|
+
# `:initial_position` - The initial position of the reflector (0-based
|
78
|
+
# numeric position, or a letter on the rotor.) Defaults
|
79
|
+
# to 0 if not specified.
|
80
|
+
#
|
81
|
+
# @param options [Hash] The options hash containing the options for the
|
82
|
+
# reflector.
|
83
|
+
# @return The newly-built reflector.
|
84
|
+
def build_reflector(options={})
|
85
|
+
reflector_kind = options.fetch(:reflector_kind, :REFLECTOR_A)
|
86
|
+
initial_position = options.fetch(:initial_position, 0)
|
87
|
+
|
88
|
+
reflector_alphabet = nil
|
89
|
+
|
90
|
+
if (reflector_kind.class.name == "Symbol")
|
91
|
+
unless RotorMachine::Reflector.constants.include?(reflector_kind)
|
92
|
+
raise ArgumentError, "Invalid reflector kind (symbol #{reflector_kind} not found)"
|
93
|
+
end
|
94
|
+
reflector_alphabet = RotorMachine::Reflector.const_get(reflector_kind)
|
95
|
+
elsif (reflector_kind.class.name == "String")
|
96
|
+
raise ArgumentError, "Invalid reflector kind (invalid length)" unless reflector_kind.length == 26
|
97
|
+
reflector_alphabet = reflector_kind.upcase
|
98
|
+
else
|
99
|
+
raise ArgumentError, "Invalid reflector kind (invalid type #{reflector_kind.class.name})"
|
100
|
+
end
|
101
|
+
|
102
|
+
if initial_position.is_a? Numeric
|
103
|
+
raise ArgumentError, "Invalid position (#{initial_position} out of range)" unless ((0..25).include?(initial_position))
|
104
|
+
elsif initial_position.is_a? String
|
105
|
+
unless RotorMachine::Reflector::ALPHABET.include?(initial_position)
|
106
|
+
raise ArgumentError, "Invalid position (invalid letter '#{initial_position}')"
|
107
|
+
end
|
108
|
+
initial_position = reflector_alphabet.index(initial_position)
|
109
|
+
else
|
110
|
+
raise ArgumentError, "Invalid position (invalid type #{initial_position.class.name})"
|
111
|
+
end
|
112
|
+
|
113
|
+
return RotorMachine::Reflector.new(reflector_alphabet, initial_position)
|
114
|
+
end
|
115
|
+
|
116
|
+
##
|
117
|
+
# Build a new {Plugboard} object and return it.
|
118
|
+
#
|
119
|
+
# @return The newly built plugboard.
|
120
|
+
def build_plugboard(options={})
|
121
|
+
return RotorMachine::Plugboard.new()
|
122
|
+
end
|
123
|
+
|
124
|
+
##
|
125
|
+
# Build a set of {Rotor}s and return them.
|
126
|
+
#
|
127
|
+
# @param rotors [Array] A list of rotor types. Each type should be either
|
128
|
+
# a symbol corresponding to a constant in the {Rotor} class, or a 26
|
129
|
+
# character string providing the sequence of letters for the rotor.
|
130
|
+
# @param initial_positions [String] The starting letter to set each rotor
|
131
|
+
# to.
|
132
|
+
# @return [Array] An array of {Rotor} objects.
|
133
|
+
def build_rotor_set(rotors=[], initial_positions=nil)
|
134
|
+
ra = rotors.each.collect { |r| build_rotor(rotor_kind: r) }
|
135
|
+
unless initial_positions.nil?
|
136
|
+
ra.each_with_index do |r, i|
|
137
|
+
unless initial_positions[i].nil?
|
138
|
+
r.position = initial_positions[i]
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
return ra
|
143
|
+
end
|
144
|
+
|
145
|
+
##
|
146
|
+
# Build a {Machine} and return it.
|
147
|
+
#
|
148
|
+
# The options hash can provide the following options:
|
149
|
+
#
|
150
|
+
# - `:rotors` - An array of {Rotor} objects. This can be constructed
|
151
|
+
# manually, through multiple calls to {#build_rotor}, or through a single
|
152
|
+
# call to {#build_rotor_set}.
|
153
|
+
# - `:reflector` - A {Reflector object.
|
154
|
+
# - `:connections` - A {Hash} of connections to make on the new {Machine}'s
|
155
|
+
# {Plugboard}.
|
156
|
+
#
|
157
|
+
# @param options The options for the newly built {Machine}.
|
158
|
+
# @return The newly constructed {Machine}.
|
159
|
+
def build_machine(options={})
|
160
|
+
rotors = options.fetch(:rotors, [])
|
161
|
+
reflector = options.fetch(:reflector, nil)
|
162
|
+
connections = options.fetch(:connections, {})
|
163
|
+
|
164
|
+
m = RotorMachine::Machine.new()
|
165
|
+
rotors.each { |r| m.rotors << r }
|
166
|
+
m.reflector = reflector
|
167
|
+
m.plugboard = RotorMachine::Plugboard.new()
|
168
|
+
unless connections.empty?
|
169
|
+
connections.each { |from, to| m.plugboard.connect(from, to) }
|
170
|
+
end
|
171
|
+
|
172
|
+
return m
|
173
|
+
end
|
174
|
+
|
175
|
+
##
|
176
|
+
# {make_rotor} is an alias for {build_rotor}
|
177
|
+
alias :make_rotor :build_rotor
|
178
|
+
|
179
|
+
##
|
180
|
+
# {make_reflector} is an alias for {build_reflector}
|
181
|
+
alias :make_reflector :build_reflector
|
182
|
+
|
183
|
+
##
|
184
|
+
# {make_plugboard} is an alias for {build_plugboard}
|
185
|
+
alias :make_plugboard :build_plugboard
|
186
|
+
|
187
|
+
##
|
188
|
+
# {make_machine} is an alias for {build_machine}
|
189
|
+
alias :make_machine :build_machine
|
190
|
+
|
191
|
+
##
|
192
|
+
# {make_rotor_set} is an alias for {build_rotor_set}
|
193
|
+
alias :make_rotor_set :build_rotor_set
|
194
|
+
end
|
195
|
+
end
|
@@ -88,12 +88,10 @@ module RotorMachine
|
|
88
88
|
# - Reflector A
|
89
89
|
# - An empty plugboard with no connections
|
90
90
|
def self.default_machine
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
machine.reflector = RotorMachine::Reflector.new(RotorMachine::Reflector::REFLECTOR_A)
|
96
|
-
machine
|
91
|
+
RotorMachine::Factory.build_machine(
|
92
|
+
rotors: RotorMachine::Factory::build_rotor_set([:ROTOR_I, :ROTOR_II, :ROTOR_III], "AAA"),
|
93
|
+
reflector: RotorMachine::Factory::build_reflector(reflector_kind: :REFLECTOR_A)
|
94
|
+
)
|
97
95
|
end
|
98
96
|
|
99
97
|
##
|
@@ -107,11 +105,7 @@ module RotorMachine
|
|
107
105
|
# A RotorMachine in this state will raise an {ArgumentError} until you
|
108
106
|
# outfit it with at least one rotor and a reflector.
|
109
107
|
def self.empty_machine
|
110
|
-
|
111
|
-
machine.rotors = []
|
112
|
-
machine.reflector = nil
|
113
|
-
machine.plugboard = RotorMachine::Plugboard.new()
|
114
|
-
machine
|
108
|
+
RotorMachine::Factory.build_machine()
|
115
109
|
end
|
116
110
|
|
117
111
|
##
|
@@ -40,14 +40,12 @@ module RotorMachine
|
|
40
40
|
# @param to [String] A single-character string designating the end
|
41
41
|
# of the connection.
|
42
42
|
def connect(from, to)
|
43
|
-
from.upcase
|
44
|
-
to.upcase
|
45
|
-
raise ArgumentError, "#{from}
|
46
|
-
raise ArgumentError, "#{to} is already connected" if (connected?(to))
|
47
|
-
raise ArgumentError, "#{from} cannot be connected to itself" if (to == from)
|
43
|
+
raise ArgumentError, "#{from.upcase} is already connected" if (connected?(from.upcase))
|
44
|
+
raise ArgumentError, "#{to.upcase} is already connected" if (connected?(to.upcase))
|
45
|
+
raise ArgumentError, "#{from.upcase} cannot be connected to itself" if (to.upcase == from.upcase)
|
48
46
|
|
49
|
-
@connections[from] = to
|
50
|
-
@connections[to] = from
|
47
|
+
@connections[from.upcase] = to.upcase
|
48
|
+
@connections[to.upcase] = from.upcase
|
51
49
|
end
|
52
50
|
|
53
51
|
##
|
@@ -14,6 +14,11 @@ module RotorMachine
|
|
14
14
|
# be broken.
|
15
15
|
class Reflector
|
16
16
|
|
17
|
+
##
|
18
|
+
# Allow querying the numeric position (ie, the initial position) of the
|
19
|
+
# reflector.
|
20
|
+
attr_reader :position
|
21
|
+
|
17
22
|
##
|
18
23
|
# The letter mapping for the German "A" reflector.
|
19
24
|
REFLECTOR_A = "EJMZALYXVBWFCRQUONTSPIKHGD".freeze
|
@@ -38,6 +43,10 @@ module RotorMachine
|
|
38
43
|
# The letter mapping for the German "ETW" reflector.
|
39
44
|
REFLECTOR_ETW = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".freeze
|
40
45
|
|
46
|
+
##
|
47
|
+
# A letter mapping for a passthrough reflector; also used by the RSpec tests
|
48
|
+
ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".freeze
|
49
|
+
|
41
50
|
##
|
42
51
|
# Initialize a new {Reflector}.
|
43
52
|
#
|
@@ -50,10 +59,31 @@ module RotorMachine
|
|
50
59
|
# an additional permutation factor for the encipherment.
|
51
60
|
def initialize(selected_reflector, start_position = 0)
|
52
61
|
@letters = selected_reflector.chars.freeze
|
53
|
-
@alphabet =
|
62
|
+
@alphabet = ALPHABET.chars.freeze
|
54
63
|
@position = start_position
|
55
64
|
end
|
56
65
|
|
66
|
+
##
|
67
|
+
# Set the position of the {Rotor}.
|
68
|
+
#
|
69
|
+
# If a numeric position is provided, an {ArgumentError} will be raised if
|
70
|
+
# the position is outside the bounds of the rotor. If an alphabetic position
|
71
|
+
# is provided, an {ArgumentError} will be raised if the supplied character
|
72
|
+
# is not a character represented on the rotor.
|
73
|
+
#
|
74
|
+
# @param pos [Numeric, String] The position of the rotor.
|
75
|
+
def position=(pos)
|
76
|
+
if pos.class.to_s == "String"
|
77
|
+
raise ArgumentError, "#{pos[0]} is not a character on the rotor" unless @letters.include?(pos[0])
|
78
|
+
@position = @letters.index(pos[0])
|
79
|
+
elsif pos.class.to_s == "Integer"
|
80
|
+
raise ArgumentError, "Position #{pos} is invalid" if (pos < 0 or pos > @letters.length)
|
81
|
+
@position = pos
|
82
|
+
else
|
83
|
+
raise ArgumentError, "Invalid argument to position= (#{pos.class.to_s})"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
57
87
|
##
|
58
88
|
# Feed a sequence of characters through the reflector, and return the
|
59
89
|
# results.
|
@@ -65,11 +95,11 @@ module RotorMachine
|
|
65
95
|
# @return [String] The results of passing the input string through the
|
66
96
|
# {Reflector}.
|
67
97
|
def reflect(input)
|
68
|
-
input.upcase.chars.each.collect { |c|
|
69
|
-
if @alphabet.include?(c) then
|
70
|
-
@letters[(@alphabet.index(c) + @position) % @alphabet.length]
|
71
|
-
else
|
72
|
-
c
|
98
|
+
input.upcase.chars.each.collect { |c|
|
99
|
+
if @alphabet.include?(c) then
|
100
|
+
@letters[(@alphabet.index(c) + @position) % @alphabet.length]
|
101
|
+
else
|
102
|
+
c
|
73
103
|
end }.join("")
|
74
104
|
end
|
75
105
|
|
@@ -86,6 +116,14 @@ module RotorMachine
|
|
86
116
|
return :CUSTOM
|
87
117
|
end
|
88
118
|
|
119
|
+
##
|
120
|
+
# Get the current letter position of the rotor.
|
121
|
+
#
|
122
|
+
# @return [String] The current letter position of the rotor.
|
123
|
+
def current_letter
|
124
|
+
@letters[self.position]
|
125
|
+
end
|
126
|
+
|
89
127
|
##
|
90
128
|
# Return a human-readable representation of the {Reflector}
|
91
129
|
#
|
data/rotor_machine.gemspec
CHANGED
@@ -26,4 +26,7 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.add_development_dependency "bundler", "~> 1.16"
|
27
27
|
spec.add_development_dependency "rake", "~> 10.0"
|
28
28
|
spec.add_development_dependency "rspec", "~> 3.0"
|
29
|
+
|
30
|
+
spec.add_development_dependency 'guard'
|
31
|
+
spec.add_development_dependency 'guard-rspec'
|
29
32
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rotor_machine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tammy Cravit
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-02-
|
11
|
+
date: 2018-02-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: tcravit_ruby_lib
|
@@ -80,6 +80,34 @@ dependencies:
|
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '3.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: guard
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: guard-rspec
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
83
111
|
description:
|
84
112
|
email:
|
85
113
|
- tammycravit@me.com
|
@@ -94,6 +122,7 @@ files:
|
|
94
122
|
- ".travis.yml"
|
95
123
|
- CODE_OF_CONDUCT.md
|
96
124
|
- Gemfile
|
125
|
+
- Guardfile
|
97
126
|
- LICENSE.txt
|
98
127
|
- README.md
|
99
128
|
- Rakefile
|
@@ -103,6 +132,7 @@ files:
|
|
103
132
|
- images/Bundesarchiv_Enigma.jpg
|
104
133
|
- images/File:Enigma_wiring_kleur.png
|
105
134
|
- lib/rotor_machine.rb
|
135
|
+
- lib/rotor_machine/factory.rb
|
106
136
|
- lib/rotor_machine/machine.rb
|
107
137
|
- lib/rotor_machine/plugboard.rb
|
108
138
|
- lib/rotor_machine/reflector.rb
|