--------------070103060009070400020400
Content-Type: text/plain; charset=us-ascii; format=flowed
Content-Transfer-Encoding: 7bit

Moin!

Here's my solution for the Solitaire Cipher quiz. It's fairly 
class-oriented and longish.

There's support for generating either a completely random key or one 
that's based on a word file.

I had to fight a nasty bug in my KeyStream generator while writing this 
(I screwed up the triple cut) -- I think it would have been easier to 
solve if I had developed test-first.

This was a nice quiz and I eagerly look forward to the next one. Thank 
you. :)

Regards,
Florian Gross


--------------070103060009070400020400
Content-Type: text/plain;
 nameolitaire.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filenameolitaire.rb"

class Array
  # Moves the item from a specified index to
  # just before the item with the specified index.
  def move(from_index, to_index)
    from_index + elf.size if from_index < 0
    to_index + elf.size if to_index < 0

    item  elf.slice!(from_index)
    self.insert(to_index, item)
  end
end

module Solitaire
  extend self

  Letters  'A' .. 'Z').to_a

  class Card < Struct.new(:face, :type)
    Faces  :ace, :two, :three, :four, :five, :six, :seven,
             :eight, :nine, :ten, :jack, :queen, :king]
    Types  :clubs, :diamonds, :hearts, :spades, :special]
    SpecialFaces  :joker_a, :joker_b]

    def self.deck
      Types.map do |type|
        if type :special
          SpecialFaces.map do |face|
            new(face, type)
          end
        else
          Faces.map do |face|
            new(face, type)
          end
        end
      end.flatten
    end

    def special?; type :special; end

    def value
      if special? then 53
      else
        Faces.index(face) + 1 + 13 * Types.index(type)
      end
    end

    def letter
      Letters[(value - 1) % 26]
    end

    def name
      if face :joker_a then "JokerA"
      elsif face :joker_b then "JokerB"
      else
        face_str  ace.to_s.capitalize.gsub(/_(\w)/) { $1.upcase }
        type_str  ype.to_s.capitalize
        face_str + " of " + type_str
      end
    end

    def compact_inspect
      if face :joker_a then "A"
      elsif face :joker_b then "B"
      else value end
    end

    def inspect
      "#<#{self.class} #{name} (#{letter}/#{value})>"
    end
    alias :to_s :inspect

    deck.each do |card|
      const_set(card.name.sub(" of ", "Of"), card)
    end
  end

  class KeyStream
    def initialize(key_method  il)
      case key_method
        when true then
          @deck  ard.deck.sort_by { rand }
        when String then
          @deck  ard.deck
          generate_letter(key_method)
        else
          @deck  ard.deck
      end
    end

    def generate_letter(seed_phrase  il)
      if seed_phrase
        seed_phrase  olitaire.clean(seed_phrase)
        seed_phrase  il if seed_phrase.empty?
      end

      result  il

      until result
        deck_size  deck.size

        # Move JokerA down one card
        old_a_pos  deck.index(Card::JokerA)
        new_a_pos  ase old_a_pos
          when deck_size - 1 then 1
          else old_a_pos + 1
        end
        @deck.move(old_a_pos, new_a_pos)

        # Move JokerB down two cards
        old_b_pos  deck.index(Card::JokerB)
        new_b_pos  ase old_b_pos
          when deck_size - 1 then 2
          when deck_size - 2 then 1
          else old_b_pos + 2
        end
        @deck.move(old_b_pos, new_b_pos)

        # Perform triple cut
        top_pos, bot_pos  @deck.index(Card::JokerA), @deck.index(Card::JokerB)].sort
        @deck.replace(
          @deck[(bot_pos + 1) .. -1] +
          @deck[top_pos .. bot_pos] + 
          @deck[0 ... top_pos])

        # Perform count cut
        top  deck.slice!(0 ... @deck.last.value)
        @deck.insert(-2, *top)

        if seed_phrase
          key  eed_phrase.slice!(0, 1)
          top  deck.slice!(0 ... Solitaire.letter_to_number(key))
          @deck.insert(-2, *top)
          result  rue if seed_phrase.empty?
        else
          # Fetch result
          card  deck[@deck.first.value]
          result  ard.letter unless card.special?
        end
      end

      return result
    end
    alias :shift :generate_letter
  end

  def letter_to_number(letter)
    Letters.index(letter) + 1
  end

  def number_to_letter(number)
    Letters[number - 1]
  end

  def clean(text)
    text.upcase.delete("^A-Z")
  end

  def pretty(text)
    clean(text).scan(/.{1,5}/).join(" ")
  end

  def encrypt(raw_text, keystream  il, pretty  rue)
    keystream || eyStream.new
    text  lean(raw_text)
    text + X" * ((text.size / 5.0).ceil * 5 - text.size)

    result  "
    0.upto(text.size - 1) do |index|
      source_num  etter_to_number(text[index, 1])
      key_num  etter_to_number(keystream.shift)
      result << number_to_letter((source_num + key_num) % 26)
    end

    result  retty(result) if pretty
    return result
  end

  def decrypt(raw_text, keystream  il, pretty  rue)
    keystream || eyStream.new
    text  lean(raw_text)

    result  "
    0.upto(text.size - 1) do |index|
      source_num  etter_to_number(text[index, 1])
      key_num  etter_to_number(keystream.shift)
      result << number_to_letter((source_num - key_num) % 26)
    end

    result  retty(result) if pretty
    return result
  end
end

if __FILE__ $0
  require 'optparse'

  options  
    :mode nil,
    :keystream nil,
    :keylength 80,
    :text nil
  }

  ARGV.options do |opts|
    script_name  ile.basename($0)
    opts.banner  Usage: ruby #{script_name} [options]"

    opts.separator ""

    opts.on("-d", "--decrypt",
      "Decrypt an encrypted message.",
      "This is the default if the message looks encrypted.") do
      options[:mode]  decrypt
    end
    opts.on("-e", "--encrypt",
      "Encrypt an unencrypted message.") do
      options[:mode]  encrypt
    end
    opts.on("-m", "--message message",
      "Specify the message.",
      "Default: Read from terminal.") do |text|
      options[:text]  ext
    end
    opts.on("-k", "--key「ャ
      「モ          ョ「ャ
      「トコ ユ   ョ「ゥ  
      ロコン  ココヒモョィゥ
    
    ョィ「ュメ「ャ 「ュュュ 「ャ ノャ
      「ユ        ョ「ャ
      「ヤ     ョ ノ   クーョ「ャ
      「ヤ          モヤトマユヤョ「ゥ  
      ロコン    
      ロコン  
    
    ョィ「ュラ「ャ 「ュュュ 「ャ
      「ユ     ョ「ャ
      「ノ         ョ「ャ
      「ヤ        ュメ ョ「ャ
      「ヤ          モヤトマユヤョ「ゥ  ゜
      ロコン  ゜
      ロコ゜ン  ゜
       

    ョ 「「

    ョィ「ュ「ャ 「ュュ「ャ
      「モ   ョ「ゥ 
       サ 
    

    ョ。
  

    ロコン  モヤトノホョ

  ロコン    ッワチィソコロチュレンオワェゥォワレッョィゥ

   ロコン
     コ 
        ョィロコンゥ  モココフロィイカゥン ョ

       「ヒコ 「 ォ モョィゥ
      ロコン  ココヒモョィゥ
     コ゜ 
      
          ョィロコ゜ンゥョィッワォッゥ
      
        モヤトナメメョ 「ラ  ァ   ァ  ョ「
         ュア
      

      ゜  ョ

      ゜  ロコン ッ カ
       ゜ シ ゜
        モヤトナメメョ 「ラ      」゜ ャ「 ォ
          「     」゜ 。「
         ュイ
      

        ン
       ョィ「「ゥョ セ ロコン
         シシ ロィ゜ゥン
      
        ョィ「 「ゥ

       「ヒコ 「 ォ 
       「ヒコ 「 ォ モョィゥ
      ロコン  ココヒモョィゥ
  

   ロコン コ
     モョィャ ロコンゥ
  
     ロコン
      モヤトナメメョ 「ラチメホノホヌコ ユ     。「
    
     モョィャ ロコンゥ
  


ュュュュュュュュュュュュュューキーアーウーカーーーケーキーエーーーイーエーーュュ