Task

Implement functions to encrypt and decrypt a message using the straddling checkerboard method. The checkerboard should take a 28 character alphabet (A-Z plus a full stop and an escape character) and two different numbers representing the blanks in the first row. The output will be a series of decimal digits.

Numbers should be encrypted by inserting the escape character before each digit, then including the digit unencrypted. This should be reversed for decryption.




class StraddlingCheckerboard
  EncodableChars = "A-Z0-9."
  SortedChars = "  ./" + [*"A".."Z"].join
 
  def initialize(board = nil)
    if board.nil?
      # create a new random board
      rest = "BCDFGHJKLMPQUVWXYZ/.".chars.shuffle
      @board = ["  ESTONIAR".chars.shuffle, rest[0..9], rest[10..19]]
    elsif board.chars.sort.join == SortedChars
      @board = board.chars.each_slice(10).to_a
    else
      raise ArgumentError, "invalid #{self.class}: #{board}"
    end
    # find the indices of the first row blanks
    @row_labels = @board[0].each_with_index.select {|v, i| v == " "}.map {|v,i| i}
 
    @mapping = {}
    @board[0].each_with_index {|char, idx| @mapping[char] = idx.to_s unless char == " "}
    @board[1..2].each_with_index do |row, row_idx|
      row.each_with_index do |char, idx|
        @mapping[char] = "%d%d" % [@row_labels[row_idx], idx]
      end
    end
  end
 
  def encode(message)
    msg = message.upcase.delete("^#{EncodableChars}")
    msg.chars.inject("") do |crypt, char|
      crypt << (char =~ /[0-9]/ ? @mapping["/"] + char : @mapping[char])
    end
  end
 
  def decode(code)
    msg = ""
    tokens = code.chars
    until tokens.empty?
      token = tokens.shift
      itoken = token.to_i
      unless @row_labels.include?(itoken)
        msg << @board[0][itoken]
      else
        token2 = tokens.shift
        if @mapping["/"] == token + token2
          msg << tokens.shift
        else
          msg << @board[1+@row_labels.index(itoken)][token2.to_i]
        end
      end
    end
    msg
  end
 
  def to_s
    @board.inject("") {|res, row| res << row.join}
  end
 
  def inspect
    "#<%s board=%p, row_labels=%p, mapping=%p>" % [self.class, to_s, @row_labels, @mapping]
  end
end