enigma_machine 0.0.1 → 0.0.2
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.
- data/.gitignore +2 -0
- data/Rakefile +3 -0
- data/enigma_machine.gemspec +2 -0
- data/lib/enigma_machine.rb +58 -1
- data/lib/enigma_machine/plugboard.rb +21 -2
- data/lib/enigma_machine/reflector.rb +16 -0
- data/lib/enigma_machine/rotor.rb +37 -0
- data/lib/enigma_machine/version.rb +1 -1
- metadata +59 -10
data/Rakefile
CHANGED
data/enigma_machine.gemspec
CHANGED
data/lib/enigma_machine.rb
CHANGED
@@ -1,8 +1,47 @@
|
|
1
1
|
class EnigmaMachine
|
2
|
-
ConfigurationError
|
2
|
+
class ConfigurationError < StandardError; end
|
3
3
|
|
4
4
|
ALPHABET = ('A'..'Z').to_a
|
5
5
|
|
6
|
+
# Constructs an enigma machine with given reflector, rotors and plugboard settings.
|
7
|
+
#
|
8
|
+
# Examples:
|
9
|
+
#
|
10
|
+
# You can use the standard reflectors and rotors:
|
11
|
+
#
|
12
|
+
# EnigmaMachine.new(
|
13
|
+
# :reflector => :B,
|
14
|
+
# :rotors => [[:i, 10], [:ii, 14], [:iii, 21]],
|
15
|
+
# :plug_pairs => %w(AP BR CM FZ GJ IL NT OV QS WX)
|
16
|
+
# )
|
17
|
+
#
|
18
|
+
# Or you can use custom reflector and rotor configurations:
|
19
|
+
#
|
20
|
+
# EnigmaMachine.new(
|
21
|
+
# :reflector => %w(AF BV CP DJ EI GO HY KR LZ MX NW TQ SU),
|
22
|
+
# :rotors => [
|
23
|
+
# ['BDFHJLCPRTXVZNYEIWGAKMUSQO_V', 10],
|
24
|
+
# ['JPGVOUMFYQBENHZRDKASXLICTW_MZ', 14],
|
25
|
+
# ['ABCDEFGHIJKLMNOPQRSTUVWXYZ_AHT', 21]
|
26
|
+
# ],
|
27
|
+
# :plug_pairs => %w(AP BR CM FZ GJ IL NT OV QS WX)
|
28
|
+
# )
|
29
|
+
#
|
30
|
+
# When specifying a custom rotor, the letters after the _ indicate the notch positions.
|
31
|
+
#
|
32
|
+
# @param config [Hash] A Hash of configuration params
|
33
|
+
# @option config [Symbol,Array<String>] :reflector Which reflector to use. Reference one of the
|
34
|
+
# standard ones by Symbol, or pass in an array of
|
35
|
+
# letter pairs for a custom configuration.
|
36
|
+
# @option config [Array<Array>] :rotors Array of details for 3 or more rotors. Specified from
|
37
|
+
# left to right. Each one is specified as a tuple of the rotor spec,
|
38
|
+
# and ring setting. The rotor_spec can be one of the standard rotors
|
39
|
+
# referenced by Symbol, or a custom rotor described by a config string
|
40
|
+
# (see Examples).
|
41
|
+
# @option config [Array<String>] :plug_pairs an Optional array of letter pairs specifying the plugboard
|
42
|
+
# configuration. If omitted, no plugboard substitutions will be performed.
|
43
|
+
#
|
44
|
+
# @raise [ConfigurationError] if an invalid configuration was specified
|
6
45
|
def initialize(config)
|
7
46
|
@reflector = Reflector.new config[:reflector]
|
8
47
|
@rotors = []
|
@@ -12,17 +51,35 @@ class EnigmaMachine
|
|
12
51
|
@plugboard = Plugboard.new(config[:plug_pairs] || [], @rotors.last)
|
13
52
|
end
|
14
53
|
|
54
|
+
# Set the rotors to the given positions.
|
55
|
+
#
|
56
|
+
# Pass in Positions for the rotors from left to right
|
57
|
+
#
|
58
|
+
# @param positions [List<String>] the positions to set the rotors to
|
59
|
+
# @return [void]
|
15
60
|
def set_rotors(*positions)
|
16
61
|
positions.each_with_index do |position, i|
|
17
62
|
@rotors[i].position = position
|
18
63
|
end
|
19
64
|
end
|
20
65
|
|
66
|
+
# Simulates pressing a given key on the machine keyboard.
|
67
|
+
#
|
68
|
+
# Advances the rotors, and then translates the letter
|
69
|
+
#
|
70
|
+
# @param letter [String] the letter to be translated
|
71
|
+
# @return [String] the translated letter
|
21
72
|
def press_key(letter)
|
22
73
|
advance_rotors
|
23
74
|
@plugboard.translate(letter)
|
24
75
|
end
|
25
76
|
|
77
|
+
# Translates the given message using the current settings
|
78
|
+
#
|
79
|
+
# This will pass through space and - unmodified, and discard any other non-alpha characters.
|
80
|
+
#
|
81
|
+
# @param message [String] the message to be translated
|
82
|
+
# @return [String] the translated message
|
26
83
|
def translate(message)
|
27
84
|
message.upcase.each_char.map do |letter|
|
28
85
|
case letter
|
@@ -1,16 +1,35 @@
|
|
1
1
|
class EnigmaMachine
|
2
2
|
class Plugboard
|
3
|
-
|
3
|
+
# Construct a new plugboard
|
4
|
+
#
|
5
|
+
# Example:
|
6
|
+
#
|
7
|
+
# Plugboard.new(%w(AB CD EF GH), @rotor)
|
8
|
+
#
|
9
|
+
# @param mapping_pairs [Array<String>] A list of letter pairs to be connected
|
10
|
+
# @param rotor [#translate] the rightmost rotor that will be called next in the processing chain
|
11
|
+
# @raise [ConfigurationError] if an invalid mapping passed in (not pairs of leters, or a letter connected more than once)
|
12
|
+
def initialize(mapping_pairs, rotor)
|
4
13
|
build_mapping(mapping_pairs)
|
5
|
-
@decorated =
|
14
|
+
@decorated = rotor
|
6
15
|
end
|
7
16
|
|
17
|
+
# Translate a letter
|
18
|
+
#
|
19
|
+
# This performs a substitution, calls the rotor to do the rest of the translation, and then
|
20
|
+
# substitutes the result on the way back out.
|
21
|
+
#
|
22
|
+
# @param letter [String] the letter to be translated
|
23
|
+
# @return [String] the translated letter
|
8
24
|
def translate(letter)
|
9
25
|
step = substitute(letter)
|
10
26
|
step = @decorated.translate(step)
|
11
27
|
substitute(step)
|
12
28
|
end
|
13
29
|
|
30
|
+
# Substitutes a letter according the configured plug pairs
|
31
|
+
# @param letter [String] the letter to be substituted
|
32
|
+
# @return [String] the substituted letter
|
14
33
|
def substitute(letter)
|
15
34
|
@mapping[letter] || letter
|
16
35
|
end
|
@@ -8,6 +8,17 @@ class EnigmaMachine
|
|
8
8
|
:Cthin => %w(AR BD CO EJ FN GT HK IV LM PW QZ SX UY),
|
9
9
|
}
|
10
10
|
|
11
|
+
# Construct a new reflector
|
12
|
+
#
|
13
|
+
# Examples:
|
14
|
+
#
|
15
|
+
# Reflector.new(:B)
|
16
|
+
# Reflector.new(%w(AY BR CU DH EQ FS GL IP JX KN MO TZ VW))
|
17
|
+
#
|
18
|
+
# @param mapping [Symbol,Array<String>] A symbol representing one of the standard reflectors or
|
19
|
+
# an array of 13 letter pairs to be swapped by the reflector
|
20
|
+
# @raise [ConfigurationError] if an invalid mapping is passed in (unrecognised standard reflector,
|
21
|
+
# not 13 pairs of letters, or a letter appearing more than once)
|
11
22
|
def initialize(mapping)
|
12
23
|
if mapping.is_a?(Symbol)
|
13
24
|
raise ConfigurationError unless STANDARD_MAPPINGS.has_key?(mapping)
|
@@ -17,6 +28,11 @@ class EnigmaMachine
|
|
17
28
|
build_mapping(mapping)
|
18
29
|
end
|
19
30
|
|
31
|
+
# @!method translate(letter)
|
32
|
+
# simply perform a substitution
|
33
|
+
#
|
34
|
+
# @param letter [String] the letter to be substituted
|
35
|
+
# @return [String] the substituted letter
|
20
36
|
alias :translate :substitute
|
21
37
|
end
|
22
38
|
end
|
data/lib/enigma_machine/rotor.rb
CHANGED
@@ -13,6 +13,20 @@ class EnigmaMachine
|
|
13
13
|
:gamma => "FSOKANUERHMBTIYCWLQPZXVGJD_", # in position 4, and hence have no notches.
|
14
14
|
}
|
15
15
|
|
16
|
+
# Construct a new rotor
|
17
|
+
#
|
18
|
+
# Examples:
|
19
|
+
#
|
20
|
+
# Rotor.new(:ii, 12, @next)
|
21
|
+
# Rotor.new("BDFHJLCPRTXVZNYEIWGAKMUSQO_V", 15, @next)
|
22
|
+
#
|
23
|
+
# When specifying a custom rotor_spec, the letters after the _ indicate the notch positions
|
24
|
+
#
|
25
|
+
# @param rotor_spec [Symbol, String] A symbol representing one of the standard rotors or
|
26
|
+
# a string specifying the mapping and notch positions (see Examples)
|
27
|
+
# @param ring_setting [Integer] The ring setting for the rotor.
|
28
|
+
# @param decorated [#translate] The next component in the processing chain (the next rotor, or the reflector)
|
29
|
+
# @raise [ConfigurationError] if an invalid rotor_spec is passed in (unrecognised standard rotor)
|
16
30
|
def initialize(rotor_spec, ring_setting, decorated)
|
17
31
|
if rotor_spec.is_a?(Symbol)
|
18
32
|
raise ConfigurationError unless STANDARD_ROTORS.has_key?(rotor_spec)
|
@@ -26,33 +40,56 @@ class EnigmaMachine
|
|
26
40
|
self.position = 'A'
|
27
41
|
end
|
28
42
|
|
43
|
+
# Set the position of the rotor
|
44
|
+
#
|
45
|
+
# @param letter [String] the letter position to set the rotor to
|
46
|
+
# @return [void]
|
29
47
|
def position=(letter)
|
30
48
|
@position = ALPHABET.index(letter)
|
31
49
|
end
|
50
|
+
# @return [String] the current position of the rotor
|
32
51
|
def position
|
33
52
|
ALPHABET[@position]
|
34
53
|
end
|
35
54
|
|
55
|
+
# Advance the position of the rotor.
|
56
|
+
# @return [void]
|
36
57
|
def advance_position
|
37
58
|
@position = (@position + 1).modulo(26)
|
38
59
|
end
|
39
60
|
|
61
|
+
# Is the rotor currently at a notch position.
|
40
62
|
def at_notch?
|
41
63
|
@notch_positions.include?(self.position)
|
42
64
|
end
|
43
65
|
|
66
|
+
# Perform the forward translation of a letter (i.e. the path towards the reflector)
|
67
|
+
#
|
68
|
+
# @param letter [String] the letter to be translated
|
69
|
+
# @return [String] the translated letter
|
44
70
|
def forward(letter)
|
45
71
|
index = add_offset ALPHABET.index(letter)
|
46
72
|
new_index = sub_offset @mapping[index]
|
47
73
|
ALPHABET[new_index]
|
48
74
|
end
|
49
75
|
|
76
|
+
# Perform the reverse translation of a letter (i.e. the path returning from the reflector)
|
77
|
+
#
|
78
|
+
# @param letter [String] the letter to be translated
|
79
|
+
# @return [String] the translated letter
|
50
80
|
def reverse(letter)
|
51
81
|
index = add_offset ALPHABET.index(letter)
|
52
82
|
new_index = sub_offset @mapping.index(index)
|
53
83
|
ALPHABET[new_index]
|
54
84
|
end
|
55
85
|
|
86
|
+
# Translate a letter
|
87
|
+
#
|
88
|
+
# This performs a forward substitution, calls the next component to do the rest of the translation, and then
|
89
|
+
# reverse substitutes the result on the way back out.
|
90
|
+
#
|
91
|
+
# @param letter [String] the letter to be translated
|
92
|
+
# @return [String] the translated letter
|
56
93
|
def translate(input)
|
57
94
|
step = forward(input)
|
58
95
|
step = @decorated.translate(step)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: enigma_machine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-07-
|
12
|
+
date: 2012-07-25 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
16
|
-
requirement:
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,47 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements:
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
25
30
|
- !ruby/object:Gem::Dependency
|
26
31
|
name: rake
|
27
|
-
requirement:
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: yard
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 0.8.2
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.8.2
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: redcarpet
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
28
65
|
none: false
|
29
66
|
requirements:
|
30
67
|
- - ! '>='
|
@@ -32,7 +69,12 @@ dependencies:
|
|
32
69
|
version: '0'
|
33
70
|
type: :development
|
34
71
|
prerelease: false
|
35
|
-
version_requirements:
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
36
78
|
description: Enigma machine simulator
|
37
79
|
email:
|
38
80
|
- alex@tomlins.org.uk
|
@@ -71,7 +113,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
71
113
|
version: '0'
|
72
114
|
segments:
|
73
115
|
- 0
|
74
|
-
hash:
|
116
|
+
hash: -1642108846997951420
|
75
117
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
118
|
none: false
|
77
119
|
requirements:
|
@@ -80,11 +122,18 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
80
122
|
version: '0'
|
81
123
|
segments:
|
82
124
|
- 0
|
83
|
-
hash:
|
125
|
+
hash: -1642108846997951420
|
84
126
|
requirements: []
|
85
127
|
rubyforge_project:
|
86
|
-
rubygems_version: 1.8.
|
128
|
+
rubygems_version: 1.8.24
|
87
129
|
signing_key:
|
88
130
|
specification_version: 3
|
89
131
|
summary: Enigma machine simulator
|
90
|
-
test_files:
|
132
|
+
test_files:
|
133
|
+
- spec/enigma_machine_spec.rb
|
134
|
+
- spec/integration_spec.rb
|
135
|
+
- spec/plugboard_spec.rb
|
136
|
+
- spec/reflector_spec.rb
|
137
|
+
- spec/rotor_spec.rb
|
138
|
+
- spec/spec_helper.rb
|
139
|
+
has_rdoc:
|