Class: RotorMachine::Rotor

Inherits:
Object
  • Object
show all
Defined in:
lib/rotor_machine/rotor.rb

Overview

Implment an Enigma machine rotor.

The Rotor is the central component of the Enigma machine’s polyalphabetic substitution cipher. Each rotor consisted of a ring with a series of internal connections and wiring which mapped input letters on the left side of the rotor to (different) output letters on the right side. The signal from the Enigma’s keyboard would pass twice through the rotor/reflector stack (and plugboard) in opposite directions before being displayed. This ensured the algorithm was symmetrical; without this property, the Enigma could not both encipher and decipher text.

Adding to the complexity of the algorithm, the rotors rotated after enciphering each character. In a standard 3-rotor Enigma machine, the rightmost rotor advanced position for each character. The middle rotor advanced one position with each full revolution of the right rotor, and the left rotor advanced one position with each full rotation of the middle rotor. These rotations permuted the signal path, so a sequence of several of the same input character would produce different output characters.

The Rotor as implemented here allows the ‘step_size` (the number of positions each rotor advances when it’s stepped) to be varied.

Constant Summary collapse

ROTOR_IC =

Provides the configuration of the German IC Enigma RotorMachine::Rotor.

"DMTWSILRUYQNKFEJCAZBPGXOHV".freeze
ROTOR_IIC =

Provides the configuration of the German IIC Enigma RotorMachine::Rotor.

"HQZGPJTMOBLNCIFDYAWVEUSRKX".freeze
ROTOR_IIIC =

Provides the configuration of the German IIIC Enigma RotorMachine::Rotor.

"UQNTLSZFMREHDPXKIBVYGJCWOA".freeze
ROTOR_I =

Provides the configuration of the German I Enigma RotorMachine::Rotor.

"JGDQOXUSCAMIFRVTPNEWKBLZYH".freeze
ROTOR_II =

Provides the configuration of the German II Enigma RotorMachine::Rotor.

"NTZPSFBOKMWRCJDIVLAEYUXHGQ".freeze
ROTOR_III =

Provides the configuration of the German III Enigma RotorMachine::Rotor.

"JVIUBHTCDYAKEQZPOSGXNRMWFL".freeze
ROTOR_UKW =

Provides the configuration of the German UKW Enigma RotorMachine::Rotor.

"QYHOGNECVPUZTFDJAXWMKISRBL".freeze
ROTOR_ETW =

Provides the configuration of the German ETW Enigma RotorMachine::Rotor.

"QWERTZUIOASDFGHJKPYXCVBNML".freeze
ALPHABET =

Provides the alphabet in order. Used for mapping rotor indices, but could also be used as a RotorMachine::Rotor configuration.

"ABCDEFGHIJKLMNOPQRSTUVWXYZ".freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(rotor, start_on = 0, step_size = 1) ⇒ Rotor

Initialize a new rotor.

Parameters:

  • rotor (String)

    The letter sequence used for the new rotor. In normal use, this should be one of the class constants which define the standard German Enigma rotors, but any sequence of 26 unique letters can be used.

  • start_on (Integer) (defaults to: 0)

    The (0-based) starting position for the rotor. Defaults to 0. To start on a specific letter, use #position= to set the rotor after creating it.

  • step_size (Integer) (defaults to: 1)

    The number of positions to step the rotor each time it is advanced. Defaults to 1.

Raises:

  • (ArgumentError)


88
89
90
91
92
93
94
# File 'lib/rotor_machine/rotor.rb', line 88

def initialize(rotor, start_on=0, step_size=1)
  raise ArgumentError, "Initialization string contains duplicate letters" unless rotor.is_uniq?
  @letters = rotor.chars.freeze
  self.position = start_on
  @step_size = step_size
  @wrapped = nil
end

Instance Attribute Details

#lettersObject (readonly)

Returns the value of attribute letters.



32
33
34
# File 'lib/rotor_machine/rotor.rb', line 32

def letters
  @letters
end

#positionObject

Query the current numeric position (0-based) of the rotor. The #position= method provides a setter for this property to allow for setting the RotorMachine::Rotor based on either a numeric position or a letter position.



30
31
32
# File 'lib/rotor_machine/rotor.rb', line 30

def position
  @position
end

#step_sizeObject

Get or set the ‘step_size` - the number of positions the rotor should advance every time it’s stepped.



37
38
39
# File 'lib/rotor_machine/rotor.rb', line 37

def step_size
  @step_size
end

Instance Method Details

#==(another_rotor) ⇒ Boolean

Compare this RotorMachine::Rotor to another one.

Returns True if the configuration of the supplied RotorMachine::Rotor matches this one, false otherwise.

Parameters:

Returns:

  • (Boolean)

    True if the configurations match, false otherwise.



212
213
214
215
216
# File 'lib/rotor_machine/rotor.rb', line 212

def ==(another_rotor)
  @letters == another_rotor.letters &&
  position == another_rotor.position &&
  step_size == another_rotor.step_size
end

#current_letterString

Get the current letter position of the rotor.

Returns:

  • (String)

    The current letter position of the rotor.



159
160
161
# File 'lib/rotor_machine/rotor.rb', line 159

def current_letter
  @letters[@position]
end

#forward(letter) ⇒ String

Return the “forward” (left-to-right) transposition of the supplied letter.

Parameters:

  • letter (String)

    The letter to encipher.

Returns:

  • (String)

    The enciphered letter.



122
123
124
125
126
127
128
# File 'lib/rotor_machine/rotor.rb', line 122

def forward(letter)
  if ALPHABET.include?(letter)
    @letters[((ALPHABET.index(letter) + self.position) % @letters.length)]
  else
    letter
  end
end

#reverse(letter) ⇒ String

Return the “reverse” (right-to-left) transposition of the supplied letter.

Parameters:

  • letter (String)

    The letter to encipher.

Returns:

  • (String)

    The enciphered letter.



136
137
138
139
140
141
142
# File 'lib/rotor_machine/rotor.rb', line 136

def reverse(letter)
  if ALPHABET.include?(letter)
    ALPHABET[((@letters.index(letter) - self.position) % @letters.length)]
  else
    letter
  end
end

#rotor_kindString

Return the current rotor’s “kind” (a string containing the mappings of the rotor.

Returns:



168
169
170
# File 'lib/rotor_machine/rotor.rb', line 168

def rotor_kind
  @letters.join("")
end

#rotor_kind_nameSymbol

Return the name of this kind of rotor.

If the rotor’s sequence matches one of the defined class constants for a standsard Enigma rotor, the name of the constant will be returned as a symbol. Otherwise, :CUSTOM is returned.

Returns:

  • (Symbol)

    The name of the kind of this rotor.



180
181
182
183
# File 'lib/rotor_machine/rotor.rb', line 180

def rotor_kind_name
  self.class.constants.each { |k| return k if (self.class.const_get(k) == rotor_kind) }
  return :CUSTOM
end

#step(step_size = @step_size) ⇒ Object

Step the rotor.

Parameters:

  • step_size (Integer) (defaults to: @step_size)

    The number of positions to step the rotor. Defaults to the value of #step_size if not provided.



149
150
151
152
153
# File 'lib/rotor_machine/rotor.rb', line 149

def step(step_size=@step_size)
  old_position = @position
  @position = (@position + step_size) % @letters.length
  @wrapped = (old_position > @position)
end

#to_sString

Generate a human-readable representation of the RotorMachine::Rotor‘s state.

Returns:

  • (String)

    The current state of the Rotor



200
201
202
# File 'lib/rotor_machine/rotor.rb', line 200

def to_s
  return "a RotorMachine::Rotor of type '#{self.rotor_kind_name}', position=#{self.position} (#{self.current_letter}), step_size=#{@step_size}"
end

#wrapped?Booleam

Check if the last #step operation caused the rotor to wrap around in position. This is used by the Machine to determine whether to advance the adjacent rotor.

Returns:

  • (Booleam)

    True if the last #step operation caused the rotor to wrap around.



192
193
194
# File 'lib/rotor_machine/rotor.rb', line 192

def wrapped?
  @wrapped
end